Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Kit Langton
87480f598f feat: unwrap ACP, Agent, Workspace, Identifier, Shell namespaces 2026-04-16 07:29:00 -04:00
68 changed files with 2494 additions and 2499 deletions

View file

@ -39,7 +39,7 @@ import { ACPSessionManager } from "./session"
import type { ACPConfig } from "./types" import type { ACPConfig } from "./types"
import { Provider } from "../provider" import { Provider } from "../provider"
import { ModelID, ProviderID } from "../provider/schema" import { ModelID, ProviderID } from "../provider/schema"
import { Agent as AgentModule } from "../agent/agent" import { Agent as AgentModule } from "../agent"
import { AppRuntime } from "@/effect/app-runtime" import { AppRuntime } from "@/effect/app-runtime"
import { Installation } from "@/installation" import { Installation } from "@/installation"
import { MessageV2 } from "@/session/message-v2" import { MessageV2 } from "@/session/message-v2"
@ -56,15 +56,14 @@ type ModelOption = { modelId: string; name: string }
const DEFAULT_VARIANT_VALUE = "default" const DEFAULT_VARIANT_VALUE = "default"
export namespace ACP { const log = Log.create({ service: "acp-agent" })
const log = Log.create({ service: "acp-agent" })
async function getContextLimit( async function getContextLimit(
sdk: OpencodeClient, sdk: OpencodeClient,
providerID: ProviderID, providerID: ProviderID,
modelID: ModelID, modelID: ModelID,
directory: string, directory: string,
): Promise<number | null> { ): Promise<number | null> {
const providers = await sdk.config const providers = await sdk.config
.providers({ directory }) .providers({ directory })
.then((x) => x.data?.providers ?? []) .then((x) => x.data?.providers ?? [])
@ -76,14 +75,14 @@ export namespace ACP {
const provider = providers.find((p) => p.id === providerID) const provider = providers.find((p) => p.id === providerID)
const model = provider?.models[modelID] const model = provider?.models[modelID]
return model?.limit.context ?? null return model?.limit.context ?? null
} }
async function sendUsageUpdate( async function sendUsageUpdate(
connection: AgentSideConnection, connection: AgentSideConnection,
sdk: OpencodeClient, sdk: OpencodeClient,
sessionID: string, sessionID: string,
directory: string, directory: string,
): Promise<void> { ): Promise<void> {
const messages = await sdk.session const messages = await sdk.session
.messages({ sessionID, directory }, { throwOnError: true }) .messages({ sessionID, directory }, { throwOnError: true })
.then((x) => x.data) .then((x) => x.data)
@ -126,17 +125,17 @@ export namespace ACP {
.catch((error) => { .catch((error) => {
log.error("failed to send usage update", { error }) log.error("failed to send usage update", { error })
}) })
} }
export async function init({ sdk: _sdk }: { sdk: OpencodeClient }) { export async function init({ sdk: _sdk }: { sdk: OpencodeClient }) {
return { return {
create: (connection: AgentSideConnection, fullConfig: ACPConfig) => { create: (connection: AgentSideConnection, fullConfig: ACPConfig) => {
return new Agent(connection, fullConfig) return new Agent(connection, fullConfig)
}, },
} }
} }
export class Agent implements ACPAgent { export class Agent implements ACPAgent {
private connection: AgentSideConnection private connection: AgentSideConnection
private config: ACPConfig private config: ACPConfig
private sdk: OpencodeClient private sdk: OpencodeClient
@ -1546,9 +1545,9 @@ export namespace ACP {
{ throwOnError: true }, { throwOnError: true },
) )
} }
} }
function toToolKind(toolName: string): ToolKind { function toToolKind(toolName: string): ToolKind {
const tool = toolName.toLocaleLowerCase() const tool = toolName.toLocaleLowerCase()
switch (tool) { switch (tool) {
case "bash": case "bash":
@ -1573,9 +1572,9 @@ export namespace ACP {
default: default:
return "other" return "other"
} }
} }
function toLocations(toolName: string, input: Record<string, any>): { path: string }[] { function toLocations(toolName: string, input: Record<string, any>): { path: string }[] {
const tool = toolName.toLocaleLowerCase() const tool = toolName.toLocaleLowerCase()
switch (tool) { switch (tool) {
case "read": case "read":
@ -1590,9 +1589,9 @@ export namespace ACP {
default: default:
return [] return []
} }
} }
async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ providerID: ProviderID; modelID: ModelID }> { async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ providerID: ProviderID; modelID: ModelID }> {
const sdk = config.sdk const sdk = config.sdk
const configured = config.defaultModel const configured = config.defaultModel
if (configured) return configured if (configured) return configured
@ -1652,11 +1651,11 @@ export namespace ACP {
if (specified) return specified if (specified) return specified
return { providerID: ProviderID.opencode, modelID: ModelID.make("big-pickle") } return { providerID: ProviderID.opencode, modelID: ModelID.make("big-pickle") }
} }
function parseUri( function parseUri(
uri: string, uri: string,
): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } { ): { type: "file"; url: string; filename: string; mime: string } | { type: "text"; text: string } {
try { try {
if (uri.startsWith("file://")) { if (uri.startsWith("file://")) {
const path = uri.slice(7) const path = uri.slice(7)
@ -1691,18 +1690,18 @@ export namespace ACP {
text: uri, text: uri,
} }
} }
} }
function getNewContent(fileOriginal: string, unifiedDiff: string): string | undefined { function getNewContent(fileOriginal: string, unifiedDiff: string): string | undefined {
const result = applyPatch(fileOriginal, unifiedDiff) const result = applyPatch(fileOriginal, unifiedDiff)
if (result === false) { if (result === false) {
log.error("Failed to apply unified diff (context mismatch)") log.error("Failed to apply unified diff (context mismatch)")
return undefined return undefined
} }
return result return result
} }
function sortProvidersByName<T extends { name: string }>(providers: T[]): T[] { function sortProvidersByName<T extends { name: string }>(providers: T[]): T[] {
return [...providers].sort((a, b) => { return [...providers].sort((a, b) => {
const nameA = a.name.toLowerCase() const nameA = a.name.toLowerCase()
const nameB = b.name.toLowerCase() const nameB = b.name.toLowerCase()
@ -1710,23 +1709,23 @@ export namespace ACP {
if (nameA > nameB) return 1 if (nameA > nameB) return 1
return 0 return 0
}) })
} }
function modelVariantsFromProviders( function modelVariantsFromProviders(
providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>, providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>,
model: { providerID: ProviderID; modelID: ModelID }, model: { providerID: ProviderID; modelID: ModelID },
): string[] { ): string[] {
const provider = providers.find((entry) => entry.id === model.providerID) const provider = providers.find((entry) => entry.id === model.providerID)
if (!provider) return [] if (!provider) return []
const modelInfo = provider.models[model.modelID] const modelInfo = provider.models[model.modelID]
if (!modelInfo?.variants) return [] if (!modelInfo?.variants) return []
return Object.keys(modelInfo.variants) return Object.keys(modelInfo.variants)
} }
function buildAvailableModels( function buildAvailableModels(
providers: Array<{ id: string; name: string; models: Record<string, any> }>, providers: Array<{ id: string; name: string; models: Record<string, any> }>,
options: { includeVariants?: boolean } = {}, options: { includeVariants?: boolean } = {},
): ModelOption[] { ): ModelOption[] {
const includeVariants = options.includeVariants ?? false const includeVariants = options.includeVariants ?? false
return providers.flatMap((provider) => { return providers.flatMap((provider) => {
const unsorted: Array<{ id: string; name: string; variants?: Record<string, any> }> = Object.values( const unsorted: Array<{ id: string; name: string; variants?: Record<string, any> }> = Object.values(
@ -1747,24 +1746,24 @@ export namespace ACP {
return [base, ...variantOptions] return [base, ...variantOptions]
}) })
}) })
} }
function formatModelIdWithVariant( function formatModelIdWithVariant(
model: { providerID: ProviderID; modelID: ModelID }, model: { providerID: ProviderID; modelID: ModelID },
variant: string | undefined, variant: string | undefined,
availableVariants: string[], availableVariants: string[],
includeVariant: boolean, includeVariant: boolean,
) { ) {
const base = `${model.providerID}/${model.modelID}` const base = `${model.providerID}/${model.modelID}`
if (!includeVariant || !variant || !availableVariants.includes(variant)) return base if (!includeVariant || !variant || !availableVariants.includes(variant)) return base
return `${base}/${variant}` return `${base}/${variant}`
} }
function buildVariantMeta(input: { function buildVariantMeta(input: {
model: { providerID: ProviderID; modelID: ModelID } model: { providerID: ProviderID; modelID: ModelID }
variant?: string variant?: string
availableVariants: string[] availableVariants: string[]
}) { }) {
return { return {
opencode: { opencode: {
modelId: `${input.model.providerID}/${input.model.modelID}`, modelId: `${input.model.providerID}/${input.model.modelID}`,
@ -1772,12 +1771,12 @@ export namespace ACP {
availableVariants: input.availableVariants, availableVariants: input.availableVariants,
}, },
} }
} }
function parseModelSelection( function parseModelSelection(
modelId: string, modelId: string,
providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>, providers: Array<{ id: string; models: Record<string, { variants?: Record<string, any> }> }>,
): { model: { providerID: ProviderID; modelID: ModelID }; variant?: string } { ): { model: { providerID: ProviderID; modelID: ModelID }; variant?: string } {
const parsed = Provider.parseModel(modelId) const parsed = Provider.parseModel(modelId)
const provider = providers.find((p) => p.id === parsed.providerID) const provider = providers.find((p) => p.id === parsed.providerID)
if (!provider) { if (!provider) {
@ -1804,13 +1803,13 @@ export namespace ACP {
} }
return { model: parsed, variant: undefined } return { model: parsed, variant: undefined }
} }
function buildConfigOptions(input: { function buildConfigOptions(input: {
currentModelId: string currentModelId: string
availableModels: ModelOption[] availableModels: ModelOption[]
modes?: { availableModes: ModeOption[]; currentModeId: string } | undefined modes?: { availableModes: ModeOption[]; currentModeId: string } | undefined
}): SessionConfigOption[] { }): SessionConfigOption[] {
const options: SessionConfigOption[] = [ const options: SessionConfigOption[] = [
{ {
id: "model", id: "model",
@ -1836,5 +1835,4 @@ export namespace ACP {
}) })
} }
return options return options
}
} }

View file

@ -0,0 +1 @@
export * as ACP from "./agent"

View file

@ -24,8 +24,7 @@ import { InstanceState } from "@/effect"
import * as Option from "effect/Option" import * as Option from "effect/Option"
import * as OtelTracer from "@effect/opentelemetry/Tracer" import * as OtelTracer from "@effect/opentelemetry/Tracer"
export namespace Agent { export const Info = z
export const Info = z
.object({ .object({
name: z.string(), name: z.string(),
description: z.string().optional(), description: z.string().optional(),
@ -50,11 +49,11 @@ export namespace Agent {
.meta({ .meta({
ref: "Agent", ref: "Agent",
}) })
export type Info = z.infer<typeof Info> export type Info = z.infer<typeof Info>
export interface Interface { export interface Interface {
readonly get: (agent: string) => Effect.Effect<Agent.Info> readonly get: (agent: string) => Effect.Effect<Info>
readonly list: () => Effect.Effect<Agent.Info[]> readonly list: () => Effect.Effect<Info[]>
readonly defaultAgent: () => Effect.Effect<string> readonly defaultAgent: () => Effect.Effect<string>
readonly generate: (input: { readonly generate: (input: {
description: string description: string
@ -64,13 +63,13 @@ export namespace Agent {
whenToUse: string whenToUse: string
systemPrompt: string systemPrompt: string
}> }>
} }
type State = Omit<Interface, "generate"> type State = Omit<Interface, "generate">
export class Service extends Context.Service<Service, Interface>()("@opencode/Agent") {} export class Service extends Context.Service<Service, Interface>()("@opencode/Agent") {}
export const layer = Layer.effect( export const layer = Layer.effect(
Service, Service,
Effect.gen(function* () { Effect.gen(function* () {
const config = yield* Config.Service const config = yield* Config.Service
@ -400,13 +399,12 @@ export namespace Agent {
}), }),
}) })
}), }),
) )
export const defaultLayer = layer.pipe( export const defaultLayer = layer.pipe(
Layer.provide(Plugin.defaultLayer), Layer.provide(Plugin.defaultLayer),
Layer.provide(Provider.defaultLayer), Layer.provide(Provider.defaultLayer),
Layer.provide(Auth.defaultLayer), Layer.provide(Auth.defaultLayer),
Layer.provide(Config.defaultLayer), Layer.provide(Config.defaultLayer),
Layer.provide(Skill.defaultLayer), Layer.provide(Skill.defaultLayer),
) )
}

View file

@ -0,0 +1 @@
export * as Agent from "./agent"

View file

@ -2,7 +2,7 @@ import { Log } from "@/util"
import { bootstrap } from "../bootstrap" import { bootstrap } from "../bootstrap"
import { cmd } from "./cmd" import { cmd } from "./cmd"
import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk" import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"
import { ACP } from "@/acp/agent" import { ACP } from "@/acp"
import { Server } from "@/server/server" import { Server } from "@/server/server"
import { createOpencodeClient } from "@opencode-ai/sdk/v2" import { createOpencodeClient } from "@opencode-ai/sdk/v2"
import { withNetworkOptions, resolveNetworkOptions } from "../network" import { withNetworkOptions, resolveNetworkOptions } from "../network"

View file

@ -3,7 +3,7 @@ import * as prompts from "@clack/prompts"
import { AppRuntime } from "@/effect/app-runtime" import { AppRuntime } from "@/effect/app-runtime"
import { UI } from "../ui" import { UI } from "../ui"
import { Global } from "../../global" import { Global } from "../../global"
import { Agent } from "../../agent/agent" import { Agent } from "../../agent"
import { Provider } from "../../provider" import { Provider } from "../../provider"
import path from "path" import path from "path"
import fs from "fs/promises" import fs from "fs/promises"

View file

@ -1,7 +1,7 @@
import { EOL } from "os" import { EOL } from "os"
import { basename } from "path" import { basename } from "path"
import { Effect } from "effect" import { Effect } from "effect"
import { Agent } from "../../../agent/agent" import { Agent } from "../../../agent"
import { Provider } from "../../../provider" import { Provider } from "../../../provider"
import { Session } from "../../../session" import { Session } from "../../../session"
import type { MessageV2 } from "../../../session/message-v2" import type { MessageV2 } from "../../../session/message-v2"

View file

@ -10,7 +10,7 @@ import { Filesystem } from "../../util"
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2" import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
import { Server } from "../../server/server" import { Server } from "../../server/server"
import { Provider } from "../../provider" import { Provider } from "../../provider"
import { Agent } from "../../agent/agent" import { Agent } from "../../agent"
import { Permission } from "../../permission" import { Permission } from "../../permission"
import { Tool } from "../../tool" import { Tool } from "../../tool"
import { GlobTool } from "../../tool/glob" import { GlobTool } from "../../tool/glob"

View file

@ -0,0 +1 @@
export * as Workspace from "./workspace"

View file

@ -1,7 +1,7 @@
import { Schema } from "effect" import { Schema } from "effect"
import z from "zod" import z from "zod"
import { Identifier } from "@/id/id" import { Identifier } from "@/id"
import { ZodOverride } from "@/util/effect-zod" import { ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"

View file

@ -25,27 +25,26 @@ import { AppRuntime } from "@/effect/app-runtime"
import { EventSequenceTable } from "@/sync/event.sql" import { EventSequenceTable } from "@/sync/event.sql"
import { waitEvent } from "./util" import { waitEvent } from "./util"
export namespace Workspace { export const Info = WorkspaceInfo.meta({
export const Info = WorkspaceInfo.meta({
ref: "Workspace", ref: "Workspace",
}) })
export type Info = z.infer<typeof Info> export type Info = z.infer<typeof Info>
export const ConnectionStatus = z.object({ export const ConnectionStatus = z.object({
workspaceID: WorkspaceID.zod, workspaceID: WorkspaceID.zod,
status: z.enum(["connected", "connecting", "disconnected", "error"]), status: z.enum(["connected", "connecting", "disconnected", "error"]),
error: z.string().optional(), error: z.string().optional(),
}) })
export type ConnectionStatus = z.infer<typeof ConnectionStatus> export type ConnectionStatus = z.infer<typeof ConnectionStatus>
const Restore = z.object({ const Restore = z.object({
workspaceID: WorkspaceID.zod, workspaceID: WorkspaceID.zod,
sessionID: SessionID.zod, sessionID: SessionID.zod,
total: z.number().int().min(0), total: z.number().int().min(0),
step: z.number().int().min(0), step: z.number().int().min(0),
}) })
export const Event = { export const Event = {
Ready: BusEvent.define( Ready: BusEvent.define(
"workspace.ready", "workspace.ready",
z.object({ z.object({
@ -60,9 +59,9 @@ export namespace Workspace {
), ),
Restore: BusEvent.define("workspace.restore", Restore), Restore: BusEvent.define("workspace.restore", Restore),
Status: BusEvent.define("workspace.status", ConnectionStatus), Status: BusEvent.define("workspace.status", ConnectionStatus),
} }
function fromRow(row: typeof WorkspaceTable.$inferSelect): Info { function fromRow(row: typeof WorkspaceTable.$inferSelect): Info {
return { return {
id: row.id, id: row.id,
type: row.type, type: row.type,
@ -72,17 +71,17 @@ export namespace Workspace {
extra: row.extra, extra: row.extra,
projectID: row.project_id, projectID: row.project_id,
} }
} }
const CreateInput = z.object({ const CreateInput = z.object({
id: WorkspaceID.zod.optional(), id: WorkspaceID.zod.optional(),
type: Info.shape.type, type: Info.shape.type,
branch: Info.shape.branch, branch: Info.shape.branch,
projectID: ProjectID.zod, projectID: ProjectID.zod,
extra: Info.shape.extra, extra: Info.shape.extra,
}) })
export const create = fn(CreateInput, async (input) => { export const create = fn(CreateInput, async (input) => {
const id = WorkspaceID.ascending(input.id) const id = WorkspaceID.ascending(input.id)
const adaptor = await getAdaptor(input.projectID, input.type) const adaptor = await getAdaptor(input.projectID, input.type)
@ -128,14 +127,14 @@ export namespace Workspace {
}) })
return info return info
}) })
const SessionRestoreInput = z.object({ const SessionRestoreInput = z.object({
workspaceID: WorkspaceID.zod, workspaceID: WorkspaceID.zod,
sessionID: SessionID.zod, sessionID: SessionID.zod,
}) })
export const sessionRestore = fn(SessionRestoreInput, async (input) => { export const sessionRestore = fn(SessionRestoreInput, async (input) => {
log.info("session restore requested", { log.info("session restore requested", {
workspaceID: input.workspaceID, workspaceID: input.workspaceID,
sessionID: input.sessionID, sessionID: input.sessionID,
@ -286,9 +285,9 @@ export namespace Workspace {
}) })
throw err throw err
} }
}) })
export function list(project: Project.Info) { export function list(project: Project.Info) {
const rows = Database.use((db) => const rows = Database.use((db) =>
db.select().from(WorkspaceTable).where(eq(WorkspaceTable.project_id, project.id)).all(), db.select().from(WorkspaceTable).where(eq(WorkspaceTable.project_id, project.id)).all(),
) )
@ -296,22 +295,22 @@ export namespace Workspace {
for (const space of spaces) startSync(space) for (const space of spaces) startSync(space)
return spaces return spaces
} }
function lookup(id: WorkspaceID) { function lookup(id: WorkspaceID) {
const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get()) const row = Database.use((db) => db.select().from(WorkspaceTable).where(eq(WorkspaceTable.id, id)).get())
if (!row) return if (!row) return
return fromRow(row) return fromRow(row)
} }
export const get = fn(WorkspaceID.zod, async (id) => { export const get = fn(WorkspaceID.zod, async (id) => {
const space = lookup(id) const space = lookup(id)
if (!space) return if (!space) return
startSync(space) startSync(space)
return space return space
}) })
export const remove = fn(WorkspaceID.zod, async (id) => { export const remove = fn(WorkspaceID.zod, async (id) => {
const sessions = Database.use((db) => const sessions = Database.use((db) =>
db.select({ id: SessionTable.id }).from(SessionTable).where(eq(SessionTable.workspace_id, id)).all(), db.select({ id: SessionTable.id }).from(SessionTable).where(eq(SessionTable.workspace_id, id)).all(),
) )
@ -334,13 +333,13 @@ export namespace Workspace {
Database.use((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run()) Database.use((db) => db.delete(WorkspaceTable).where(eq(WorkspaceTable.id, id)).run())
return info return info
} }
}) })
const connections = new Map<WorkspaceID, ConnectionStatus>() const connections = new Map<WorkspaceID, ConnectionStatus>()
const aborts = new Map<WorkspaceID, AbortController>() const aborts = new Map<WorkspaceID, AbortController>()
const TIMEOUT = 5000 const TIMEOUT = 5000
function setStatus(id: WorkspaceID, status: ConnectionStatus["status"], error?: string) { function setStatus(id: WorkspaceID, status: ConnectionStatus["status"], error?: string) {
const prev = connections.get(id) const prev = connections.get(id)
if (prev?.status === status && prev?.error === error) return if (prev?.status === status && prev?.error === error) return
const next = { workspaceID: id, status, error } const next = { workspaceID: id, status, error }
@ -358,13 +357,13 @@ export namespace Workspace {
properties: next, properties: next,
}, },
}) })
} }
export function status(): ConnectionStatus[] { export function status(): ConnectionStatus[] {
return [...connections.values()] return [...connections.values()]
} }
function synced(state: Record<string, number>) { function synced(state: Record<string, number>) {
const ids = Object.keys(state) const ids = Object.keys(state)
if (ids.length === 0) return true if (ids.length === 0) return true
@ -384,13 +383,13 @@ export namespace Workspace {
return ids.every((id) => { return ids.every((id) => {
return (done[id] ?? -1) >= state[id] return (done[id] ?? -1) >= state[id]
}) })
} }
export async function isSyncing(workspaceID: WorkspaceID) { export async function isSyncing(workspaceID: WorkspaceID) {
return aborts.has(workspaceID) return aborts.has(workspaceID)
} }
export async function waitForSync(workspaceID: WorkspaceID, state: Record<string, number>, signal?: AbortSignal) { export async function waitForSync(workspaceID: WorkspaceID, state: Record<string, number>, signal?: AbortSignal) {
if (synced(state)) return if (synced(state)) return
try { try {
@ -408,19 +407,19 @@ export namespace Workspace {
if (signal?.aborted) throw signal.reason ?? new Error("Request aborted") if (signal?.aborted) throw signal.reason ?? new Error("Request aborted")
throw new Error(`Timed out waiting for sync fence: ${JSON.stringify(state)}`) throw new Error(`Timed out waiting for sync fence: ${JSON.stringify(state)}`)
} }
} }
const log = Log.create({ service: "workspace-sync" }) const log = Log.create({ service: "workspace-sync" })
function route(url: string | URL, path: string) { function route(url: string | URL, path: string) {
const next = new URL(url) const next = new URL(url)
next.pathname = `${next.pathname.replace(/\/$/, "")}${path}` next.pathname = `${next.pathname.replace(/\/$/, "")}${path}`
next.search = "" next.search = ""
next.hash = "" next.hash = ""
return next return next
} }
async function syncWorkspace(space: Info, signal: AbortSignal) { async function syncWorkspace(space: Info, signal: AbortSignal) {
while (!signal.aborted) { while (!signal.aborted) {
log.info("connecting to global sync", { workspace: space.name }) log.info("connecting to global sync", { workspace: space.name })
setStatus(space.id, "connecting") setStatus(space.id, "connecting")
@ -484,9 +483,9 @@ export namespace Workspace {
// TODO: Implement exponential backoff // TODO: Implement exponential backoff
await sleep(1000) await sleep(1000)
} }
} }
async function startSync(space: Info) { async function startSync(space: Info) {
if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) return
const adaptor = await getAdaptor(space.projectID, space.type) const adaptor = await getAdaptor(space.projectID, space.type)
@ -515,11 +514,10 @@ export namespace Workspace {
error, error,
}) })
}) })
} }
function stopSync(id: WorkspaceID) { function stopSync(id: WorkspaceID) {
aborts.get(id)?.abort() aborts.get(id)?.abort()
aborts.delete(id) aborts.delete(id)
connections.delete(id) connections.delete(id)
}
} }

View file

@ -17,7 +17,7 @@ import { Snapshot } from "@/snapshot"
import { Plugin } from "@/plugin" import { Plugin } from "@/plugin"
import { Provider } from "@/provider" import { Provider } from "@/provider"
import { ProviderAuth } from "@/provider" import { ProviderAuth } from "@/provider"
import { Agent } from "@/agent/agent" import { Agent } from "@/agent"
import { Skill } from "@/skill" import { Skill } from "@/skill"
import { Discovery } from "@/skill/discovery" import { Discovery } from "@/skill/discovery"
import { Question } from "@/question" import { Question } from "@/question"

View file

@ -1,8 +1,7 @@
import z from "zod" import z from "zod"
import { randomBytes } from "crypto" import { randomBytes } from "crypto"
export namespace Identifier { const prefixes = {
const prefixes = {
event: "evt", event: "evt",
session: "ses", session: "ses",
message: "msg", message: "msg",
@ -14,27 +13,27 @@ export namespace Identifier {
tool: "tool", tool: "tool",
workspace: "wrk", workspace: "wrk",
entry: "ent", entry: "ent",
} as const } as const
export function schema(prefix: keyof typeof prefixes) { export function schema(prefix: keyof typeof prefixes) {
return z.string().startsWith(prefixes[prefix]) return z.string().startsWith(prefixes[prefix])
} }
const LENGTH = 26 const LENGTH = 26
// State for monotonic ID generation // State for monotonic ID generation
let lastTimestamp = 0 let lastTimestamp = 0
let counter = 0 let counter = 0
export function ascending(prefix: keyof typeof prefixes, given?: string) { export function ascending(prefix: keyof typeof prefixes, given?: string) {
return generateID(prefix, "ascending", given) return generateID(prefix, "ascending", given)
} }
export function descending(prefix: keyof typeof prefixes, given?: string) { export function descending(prefix: keyof typeof prefixes, given?: string) {
return generateID(prefix, "descending", given) return generateID(prefix, "descending", given)
} }
function generateID(prefix: keyof typeof prefixes, direction: "descending" | "ascending", given?: string): string { function generateID(prefix: keyof typeof prefixes, direction: "descending" | "ascending", given?: string): string {
if (!given) { if (!given) {
return create(prefixes[prefix], direction) return create(prefixes[prefix], direction)
} }
@ -43,9 +42,9 @@ export namespace Identifier {
throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`) throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`)
} }
return given return given
} }
function randomBase62(length: number): string { function randomBase62(length: number): string {
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
let result = "" let result = ""
const bytes = randomBytes(length) const bytes = randomBytes(length)
@ -53,9 +52,9 @@ export namespace Identifier {
result += chars[bytes[i] % 62] result += chars[bytes[i] % 62]
} }
return result return result
} }
export function create(prefix: string, direction: "descending" | "ascending", timestamp?: number): string { export function create(prefix: string, direction: "descending" | "ascending", timestamp?: number): string {
const currentTimestamp = timestamp ?? Date.now() const currentTimestamp = timestamp ?? Date.now()
if (currentTimestamp !== lastTimestamp) { if (currentTimestamp !== lastTimestamp) {
@ -74,13 +73,12 @@ export namespace Identifier {
} }
return prefix + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12) return prefix + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12)
} }
/** Extract timestamp from an ascending ID. Does not work with descending IDs. */ /** Extract timestamp from an ascending ID. Does not work with descending IDs. */
export function timestamp(id: string): number { export function timestamp(id: string): number {
const prefix = id.split("_")[0] const prefix = id.split("_")[0]
const hex = id.slice(prefix.length + 1, prefix.length + 13) const hex = id.slice(prefix.length + 1, prefix.length + 13)
const encoded = BigInt("0x" + hex) const encoded = BigInt("0x" + hex)
return Number(encoded / BigInt(0x1000)) return Number(encoded / BigInt(0x1000))
}
} }

View file

@ -0,0 +1 @@
export * as Identifier from "./id"

View file

@ -1,7 +1,7 @@
import { Schema } from "effect" import { Schema } from "effect"
import z from "zod" import z from "zod"
import { Identifier } from "@/id/id" import { Identifier } from "@/id"
import { ZodOverride } from "@/util/effect-zod" import { ZodOverride } from "@/util/effect-zod"
import { Newtype } from "@/util/schema" import { Newtype } from "@/util/schema"

View file

@ -1,7 +1,7 @@
import { Schema } from "effect" import { Schema } from "effect"
import z from "zod" import z from "zod"
import { Identifier } from "@/id/id" import { Identifier } from "@/id"
import { ZodOverride } from "@/util/effect-zod" import { ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"

View file

@ -6,7 +6,7 @@ import type { Proc } from "#pty"
import z from "zod" import z from "zod"
import { Log } from "../util" import { Log } from "../util"
import { lazy } from "@opencode-ai/shared/util/lazy" import { lazy } from "@opencode-ai/shared/util/lazy"
import { Shell } from "@/shell/shell" import { Shell } from "@/shell"
import { Plugin } from "@/plugin" import { Plugin } from "@/plugin"
import { PtyID } from "./schema" import { PtyID } from "./schema"
import { Effect, Layer, Context } from "effect" import { Effect, Layer, Context } from "effect"

View file

@ -1,7 +1,7 @@
import { Schema } from "effect" import { Schema } from "effect"
import z from "zod" import z from "zod"
import { Identifier } from "@/id/id" import { Identifier } from "@/id"
import { ZodOverride } from "@/util/effect-zod" import { ZodOverride } from "@/util/effect-zod"
import { Newtype } from "@/util/schema" import { Newtype } from "@/util/schema"

View file

@ -1,7 +1,7 @@
import type { MiddlewareHandler } from "hono" import type { MiddlewareHandler } from "hono"
import { Database, inArray } from "@/storage" import { Database, inArray } from "@/storage"
import { EventSequenceTable } from "@/sync/event.sql" import { EventSequenceTable } from "@/sync/event.sql"
import { Workspace } from "@/control-plane/workspace" import { Workspace } from "@/control-plane"
import type { WorkspaceID } from "@/control-plane/schema" import type { WorkspaceID } from "@/control-plane/schema"
import { Log } from "@/util" import { Log } from "@/util"

View file

@ -17,7 +17,7 @@ import { errors } from "../error"
import { lazy } from "../../util/lazy" import { lazy } from "../../util/lazy"
import { Effect, Option } from "effect" import { Effect, Option } from "effect"
import { WorkspaceRoutes } from "./workspace" import { WorkspaceRoutes } from "./workspace"
import { Agent } from "@/agent/agent" import { Agent } from "@/agent"
const ConsoleOrgOption = z.object({ const ConsoleOrgOption = z.object({
accountID: z.string(), accountID: z.string(),

View file

@ -7,7 +7,7 @@ import { Format } from "../../format"
import { TuiRoutes } from "./tui" import { TuiRoutes } from "./tui"
import { Instance } from "../../project/instance" import { Instance } from "../../project/instance"
import { Vcs } from "../../project" import { Vcs } from "../../project"
import { Agent } from "../../agent/agent" import { Agent } from "../../agent"
import { Skill } from "../../skill" import { Skill } from "../../skill"
import { Global } from "../../global" import { Global } from "../../global"
import { LSP } from "../../lsp" import { LSP } from "../../lsp"

View file

@ -2,7 +2,7 @@ import type { MiddlewareHandler } from "hono"
import type { UpgradeWebSocket } from "hono/ws" import type { UpgradeWebSocket } from "hono/ws"
import { getAdaptor } from "@/control-plane/adaptors" import { getAdaptor } from "@/control-plane/adaptors"
import { WorkspaceID } from "@/control-plane/schema" import { WorkspaceID } from "@/control-plane/schema"
import { Workspace } from "@/control-plane/workspace" import { Workspace } from "@/control-plane"
import { ServerProxy } from "../proxy" import { ServerProxy } from "../proxy"
import { Instance } from "@/project/instance" import { Instance } from "@/project/instance"
import { InstanceBootstrap } from "@/project/bootstrap" import { InstanceBootstrap } from "@/project/bootstrap"

View file

@ -15,7 +15,7 @@ import { SessionSummary } from "@/session/summary"
import { Todo } from "../../session/todo" import { Todo } from "../../session/todo"
import { Effect } from "effect" import { Effect } from "effect"
import { AppRuntime } from "../../effect/app-runtime" import { AppRuntime } from "../../effect/app-runtime"
import { Agent } from "../../agent/agent" import { Agent } from "../../agent"
import { Snapshot } from "@/snapshot" import { Snapshot } from "@/snapshot"
import { Command } from "../../command" import { Command } from "../../command"
import { Log } from "../../util" import { Log } from "../../util"

View file

@ -2,7 +2,7 @@ import { Hono } from "hono"
import { describeRoute, resolver, validator } from "hono-openapi" import { describeRoute, resolver, validator } from "hono-openapi"
import z from "zod" import z from "zod"
import { listAdaptors } from "../../control-plane/adaptors" import { listAdaptors } from "../../control-plane/adaptors"
import { Workspace } from "../../control-plane/workspace" import { Workspace } from "../../control-plane"
import { Instance } from "../../project/instance" import { Instance } from "../../project/instance"
import { errors } from "../error" import { errors } from "../error"
import { lazy } from "../../util/lazy" import { lazy } from "../../util/lazy"

View file

@ -3,7 +3,7 @@ import type { UpgradeWebSocket } from "hono/ws"
import { Log } from "@/util" import { Log } from "@/util"
import * as Fence from "./fence" import * as Fence from "./fence"
import type { WorkspaceID } from "@/control-plane/schema" import type { WorkspaceID } from "@/control-plane/schema"
import { Workspace } from "@/control-plane/workspace" import { Workspace } from "@/control-plane"
const hop = new Set([ const hop = new Set([
"connection", "connection",

View file

@ -8,7 +8,7 @@ import z from "zod"
import { Token } from "../util" import { Token } from "../util"
import { Log } from "../util" import { Log } from "../util"
import { SessionProcessor } from "./processor" import { SessionProcessor } from "./processor"
import { Agent } from "@/agent/agent" import { Agent } from "@/agent"
import { Plugin } from "@/plugin" import { Plugin } from "@/plugin"
import { Config } from "@/config" import { Config } from "@/config"
import { NotFoundError } from "@/storage" import { NotFoundError } from "@/storage"

View file

@ -8,7 +8,7 @@ import { GitLabWorkflowLanguageModel } from "gitlab-ai-provider"
import { ProviderTransform } from "@/provider" import { ProviderTransform } from "@/provider"
import { Config } from "@/config" import { Config } from "@/config"
import { Instance } from "@/project/instance" import { Instance } from "@/project/instance"
import type { Agent } from "@/agent/agent" import type { Agent } from "@/agent"
import type { MessageV2 } from "./message-v2" import type { MessageV2 } from "./message-v2"
import { Plugin } from "@/plugin" import { Plugin } from "@/plugin"
import { SystemPrompt } from "./system" import { SystemPrompt } from "./system"

View file

@ -1,6 +1,6 @@
import { Cause, Deferred, Effect, Layer, Context, Scope } from "effect" import { Cause, Deferred, Effect, Layer, Context, Scope } from "effect"
import * as Stream from "effect/Stream" import * as Stream from "effect/Stream"
import { Agent } from "@/agent/agent" import { Agent } from "@/agent"
import { Bus } from "@/bus" import { Bus } from "@/bus"
import { Config } from "@/config" import { Config } from "@/config"
import { Permission } from "@/permission" import { Permission } from "@/permission"

View file

@ -6,7 +6,7 @@ import { MessageV2 } from "./message-v2"
import { Log } from "../util" import { Log } from "../util"
import { SessionRevert } from "./revert" import { SessionRevert } from "./revert"
import * as Session from "./session" import * as Session from "./session"
import { Agent } from "../agent/agent" import { Agent } from "../agent"
import { Provider } from "../provider" import { Provider } from "../provider"
import { ModelID, ProviderID } from "../provider/schema" import { ModelID, ProviderID } from "../provider/schema"
import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai" import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai"
@ -38,7 +38,7 @@ import { Tool } from "@/tool"
import { Permission } from "@/permission" import { Permission } from "@/permission"
import { SessionStatus } from "./status" import { SessionStatus } from "./status"
import { LLM } from "./llm" import { LLM } from "./llm"
import { Shell } from "@/shell/shell" import { Shell } from "@/shell"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Truncate } from "@/tool" import { Truncate } from "@/tool"
import { decodeDataUrl } from "@/util/data-url" import { decodeDataUrl } from "@/util/data-url"

View file

@ -1,7 +1,7 @@
import { Schema } from "effect" import { Schema } from "effect"
import z from "zod" import z from "zod"
import { Identifier } from "@/id/id" import { Identifier } from "@/id"
import { ZodOverride } from "@/util/effect-zod" import { ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"

View file

@ -12,7 +12,7 @@ import PROMPT_KIMI from "./prompt/kimi.txt"
import PROMPT_CODEX from "./prompt/codex.txt" import PROMPT_CODEX from "./prompt/codex.txt"
import PROMPT_TRINITY from "./prompt/trinity.txt" import PROMPT_TRINITY from "./prompt/trinity.txt"
import type { Provider } from "@/provider" import type { Provider } from "@/provider"
import type { Agent } from "@/agent/agent" import type { Agent } from "@/agent"
import { Permission } from "@/permission" import { Permission } from "@/permission"
import { Skill } from "@/skill" import { Skill } from "@/skill"

View file

@ -0,0 +1 @@
export * as Shell from "./shell"

View file

@ -8,12 +8,11 @@ import { setTimeout as sleep } from "node:timers/promises"
const SIGKILL_TIMEOUT_MS = 200 const SIGKILL_TIMEOUT_MS = 200
export namespace Shell { const BLACKLIST = new Set(["fish", "nu"])
const BLACKLIST = new Set(["fish", "nu"]) const LOGIN = new Set(["bash", "dash", "fish", "ksh", "sh", "zsh"])
const LOGIN = new Set(["bash", "dash", "fish", "ksh", "sh", "zsh"]) const POSIX = new Set(["bash", "dash", "ksh", "sh", "zsh"])
const POSIX = new Set(["bash", "dash", "ksh", "sh", "zsh"])
export async function killTree(proc: ChildProcess, opts?: { exited?: () => boolean }): Promise<void> { export async function killTree(proc: ChildProcess, opts?: { exited?: () => boolean }): Promise<void> {
const pid = proc.pid const pid = proc.pid
if (!pid || opts?.exited?.()) return if (!pid || opts?.exited?.()) return
@ -42,9 +41,9 @@ export namespace Shell {
proc.kill("SIGKILL") proc.kill("SIGKILL")
} }
} }
} }
function full(file: string) { function full(file: string) {
if (process.platform !== "win32") return file if (process.platform !== "win32") return file
const shell = Filesystem.windowsPath(file) const shell = Filesystem.windowsPath(file)
if (path.win32.dirname(shell) !== ".") { if (path.win32.dirname(shell) !== ".") {
@ -52,34 +51,34 @@ export namespace Shell {
return shell return shell
} }
return which(shell) || shell return which(shell) || shell
} }
function pick() { function pick() {
const pwsh = which("pwsh.exe") const pwsh = which("pwsh.exe")
if (pwsh) return pwsh if (pwsh) return pwsh
const powershell = which("powershell.exe") const powershell = which("powershell.exe")
if (powershell) return powershell if (powershell) return powershell
} }
function select(file: string | undefined, opts?: { acceptable?: boolean }) { function select(file: string | undefined, opts?: { acceptable?: boolean }) {
if (file && (!opts?.acceptable || !BLACKLIST.has(name(file)))) return full(file) if (file && (!opts?.acceptable || !BLACKLIST.has(name(file)))) return full(file)
if (process.platform === "win32") { if (process.platform === "win32") {
const shell = pick() const shell = pick()
if (shell) return shell if (shell) return shell
} }
return fallback() return fallback()
} }
export function gitbash() { export function gitbash() {
if (process.platform !== "win32") return if (process.platform !== "win32") return
if (Flag.OPENCODE_GIT_BASH_PATH) return Flag.OPENCODE_GIT_BASH_PATH if (Flag.OPENCODE_GIT_BASH_PATH) return Flag.OPENCODE_GIT_BASH_PATH
const git = which("git") const git = which("git")
if (!git) return if (!git) return
const file = path.join(git, "..", "..", "bin", "bash.exe") const file = path.join(git, "..", "..", "bin", "bash.exe")
if (Filesystem.stat(file)?.size) return file if (Filesystem.stat(file)?.size) return file
} }
function fallback() { function fallback() {
if (process.platform === "win32") { if (process.platform === "win32") {
const file = gitbash() const file = gitbash()
if (file) return file if (file) return file
@ -89,22 +88,21 @@ export namespace Shell {
const bash = which("bash") const bash = which("bash")
if (bash) return bash if (bash) return bash
return "/bin/sh" return "/bin/sh"
} }
export function name(file: string) { export function name(file: string) {
if (process.platform === "win32") return path.win32.parse(Filesystem.windowsPath(file)).name.toLowerCase() if (process.platform === "win32") return path.win32.parse(Filesystem.windowsPath(file)).name.toLowerCase()
return path.basename(file).toLowerCase() return path.basename(file).toLowerCase()
}
export function login(file: string) {
return LOGIN.has(name(file))
}
export function posix(file: string) {
return POSIX.has(name(file))
}
export const preferred = lazy(() => select(process.env.SHELL))
export const acceptable = lazy(() => select(process.env.SHELL, { acceptable: true }))
} }
export function login(file: string) {
return LOGIN.has(name(file))
}
export function posix(file: string) {
return POSIX.has(name(file))
}
export const preferred = lazy(() => select(process.env.SHELL))
export const acceptable = lazy(() => select(process.env.SHELL, { acceptable: true }))

View file

@ -4,7 +4,7 @@ import { pathToFileURL } from "url"
import z from "zod" import z from "zod"
import { Effect, Layer, Context } from "effect" import { Effect, Layer, Context } from "effect"
import { NamedError } from "@opencode-ai/shared/util/error" import { NamedError } from "@opencode-ai/shared/util/error"
import type { Agent } from "@/agent/agent" import type { Agent } from "@/agent"
import { Bus } from "@/bus" import { Bus } from "@/bus"
import { InstanceState } from "@/effect" import { InstanceState } from "@/effect"
import { Flag } from "@/flag/flag" import { Flag } from "@/flag/flag"

View file

@ -1,7 +1,7 @@
import { Schema } from "effect" import { Schema } from "effect"
import z from "zod" import z from "zod"
import { Identifier } from "@/id/id" import { Identifier } from "@/id"
import { ZodOverride } from "@/util/effect-zod" import { ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"

View file

@ -12,7 +12,7 @@ import { Language, type Node } from "web-tree-sitter"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { fileURLToPath } from "url" import { fileURLToPath } from "url"
import { Flag } from "@/flag/flag" import { Flag } from "@/flag/flag"
import { Shell } from "@/shell/shell" import { Shell } from "@/shell"
import { BashArity } from "@/permission/arity" import { BashArity } from "@/permission/arity"
import * as Truncate from "./truncate" import * as Truncate from "./truncate"

View file

@ -43,7 +43,7 @@ import { FileTime } from "../file/time"
import { Instruction } from "../session/instruction" import { Instruction } from "../session/instruction"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Bus } from "../bus" import { Bus } from "../bus"
import { Agent } from "../agent/agent" import { Agent } from "../agent"
import { Skill } from "../skill" import { Skill } from "../skill"
import { Permission } from "@/permission" import { Permission } from "@/permission"

View file

@ -1,7 +1,7 @@
import { Schema } from "effect" import { Schema } from "effect"
import z from "zod" import z from "zod"
import { Identifier } from "@/id/id" import { Identifier } from "@/id"
import { ZodOverride } from "@/util/effect-zod" import { ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"

View file

@ -4,7 +4,7 @@ import z from "zod"
import { Session } from "../session" import { Session } from "../session"
import { SessionID, MessageID } from "../session/schema" import { SessionID, MessageID } from "../session/schema"
import { MessageV2 } from "../session/message-v2" import { MessageV2 } from "../session/message-v2"
import { Agent } from "../agent/agent" import { Agent } from "../agent"
import type { SessionPrompt } from "../session/prompt" import type { SessionPrompt } from "../session/prompt"
import { Config } from "../config" import { Config } from "../config"
import { Effect } from "effect" import { Effect } from "effect"

View file

@ -4,7 +4,7 @@ import type { MessageV2 } from "../session/message-v2"
import type { Permission } from "../permission" import type { Permission } from "../permission"
import type { SessionID, MessageID } from "../session/schema" import type { SessionID, MessageID } from "../session/schema"
import * as Truncate from "./truncate" import * as Truncate from "./truncate"
import { Agent } from "@/agent/agent" import { Agent } from "@/agent"
interface Metadata { interface Metadata {
[key: string]: any [key: string]: any

View file

@ -1,10 +1,10 @@
import { NodePath } from "@effect/platform-node" import { NodePath } from "@effect/platform-node"
import { Cause, Duration, Effect, Layer, Schedule, Context } from "effect" import { Cause, Duration, Effect, Layer, Schedule, Context } from "effect"
import path from "path" import path from "path"
import type { Agent } from "../agent/agent" import type { Agent } from "../agent"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { evaluate } from "@/permission/evaluate" import { evaluate } from "@/permission/evaluate"
import { Identifier } from "../id/id" import { Identifier } from "../id"
import { Log } from "../util" import { Log } from "../util"
import { ToolID } from "./schema" import { ToolID } from "./schema"
import { TRUNCATION_DIR } from "./truncation-dir" import { TRUNCATION_DIR } from "./truncation-dir"

View file

@ -1,4 +1,4 @@
import { Identifier } from "@/id/id" import { Identifier } from "@/id"
import { withStatics } from "@/util/schema" import { withStatics } from "@/util/schema"
import * as DateTime from "effect/DateTime" import * as DateTime from "effect/DateTime"
import { Schema } from "effect" import { Schema } from "effect"

View file

@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { ACP } from "../../src/acp/agent" import { ACP } from "../../src/acp"
import type { Agent as ACPAgent } from "@agentclientprotocol/sdk" import type { Agent as ACPAgent } from "@agentclientprotocol/sdk"
/** /**

View file

@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import { ACP } from "../../src/acp/agent" import { ACP } from "../../src/acp"
import type { AgentSideConnection } from "@agentclientprotocol/sdk" import type { AgentSideConnection } from "@agentclientprotocol/sdk"
import type { Event, EventMessagePartUpdated, ToolStatePending, ToolStateRunning } from "@opencode-ai/sdk/v2" import type { Event, EventMessagePartUpdated, ToolStatePending, ToolStateRunning } from "@opencode-ai/sdk/v2"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"

View file

@ -3,7 +3,7 @@ import { Effect } from "effect"
import path from "path" import path from "path"
import { provideInstance, tmpdir } from "../fixture/fixture" import { provideInstance, tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Permission } from "../../src/permission" import { Permission } from "../../src/permission"
// Helper to evaluate permission for a tool with wildcard pattern // Helper to evaluate permission for a tool with wildcard pattern

View file

@ -4,7 +4,7 @@ import path from "path"
import { provideInstance, tmpdir } from "../fixture/fixture" import { provideInstance, tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Config } from "../../src/config" import { Config } from "../../src/config"
import { Agent as AgentSvc } from "../../src/agent/agent" import { Agent as AgentSvc } from "../../src/agent"
import { Color } from "../../src/util" import { Color } from "../../src/util"
import { AppRuntime } from "../../src/effect/app-runtime" import { AppRuntime } from "../../src/effect/app-runtime"

View file

@ -9,7 +9,7 @@ process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1"
const { Flag } = await import("../../src/flag/flag") const { Flag } = await import("../../src/flag/flag")
const { Plugin } = await import("../../src/plugin/index") const { Plugin } = await import("../../src/plugin/index")
const { Workspace } = await import("../../src/control-plane/workspace") const { Workspace } = await import("../../src/control-plane")
const { Instance } = await import("../../src/project/instance") const { Instance } = await import("../../src/project/instance")
const experimental = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES const experimental = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES

View file

@ -3,7 +3,7 @@ import { AppRuntime } from "../../src/effect/app-runtime"
import { Effect } from "effect" import { Effect } from "effect"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Pty } from "../../src/pty" import { Pty } from "../../src/pty"
import { Shell } from "../../src/shell/shell" import { Shell } from "../../src/shell"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
Shell.preferred.reset() Shell.preferred.reset()

View file

@ -5,7 +5,7 @@ import * as Stream from "effect/Stream"
import z from "zod" import z from "zod"
import { Bus } from "../../src/bus" import { Bus } from "../../src/bus"
import { Config } from "../../src/config" import { Config } from "../../src/config"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { LLM } from "../../src/session/llm" import { LLM } from "../../src/session/llm"
import { SessionCompaction } from "../../src/session/compaction" import { SessionCompaction } from "../../src/session/compaction"
import { Token } from "../../src/util" import { Token } from "../../src/util"

View file

@ -12,7 +12,7 @@ import { ModelsDev } from "../../src/provider"
import { ProviderID, ModelID } from "../../src/provider/schema" import { ProviderID, ModelID } from "../../src/provider/schema"
import { Filesystem } from "../../src/util" import { Filesystem } from "../../src/util"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
import type { Agent } from "../../src/agent/agent" import type { Agent } from "../../src/agent"
import { MessageV2 } from "../../src/session/message-v2" import { MessageV2 } from "../../src/session/message-v2"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
import { AppRuntime } from "../../src/effect/app-runtime" import { AppRuntime } from "../../src/effect/app-runtime"

View file

@ -2,8 +2,8 @@ import { NodeFileSystem } from "@effect/platform-node"
import { expect } from "bun:test" import { expect } from "bun:test"
import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { Cause, Effect, Exit, Fiber, Layer } from "effect"
import path from "path" import path from "path"
import type { Agent } from "../../src/agent/agent" import type { Agent } from "../../src/agent"
import { Agent as AgentSvc } from "../../src/agent/agent" import { Agent as AgentSvc } from "../../src/agent"
import { Bus } from "../../src/bus" import { Bus } from "../../src/bus"
import { Config } from "../../src/config" import { Config } from "../../src/config"
import { Permission } from "../../src/permission" import { Permission } from "../../src/permission"

View file

@ -3,7 +3,7 @@ import { FetchHttpClient } from "effect/unstable/http"
import { expect } from "bun:test" import { expect } from "bun:test"
import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { Cause, Effect, Exit, Fiber, Layer } from "effect"
import path from "path" import path from "path"
import { Agent as AgentSvc } from "../../src/agent/agent" import { Agent as AgentSvc } from "../../src/agent"
import { Bus } from "../../src/bus" import { Bus } from "../../src/bus"
import { Command } from "../../src/command" import { Command } from "../../src/command"
import { Config } from "../../src/config" import { Config } from "../../src/config"
@ -32,7 +32,7 @@ import { MessageID, PartID, SessionID } from "../../src/session/schema"
import { SessionStatus } from "../../src/session/status" import { SessionStatus } from "../../src/session/status"
import { Skill } from "../../src/skill" import { Skill } from "../../src/skill"
import { SystemPrompt } from "../../src/session/system" import { SystemPrompt } from "../../src/session/system"
import { Shell } from "../../src/shell/shell" import { Shell } from "../../src/shell"
import { Snapshot } from "../../src/snapshot" import { Snapshot } from "../../src/snapshot"
import { ToolRegistry } from "../../src/tool" import { ToolRegistry } from "../../src/tool"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"

View file

@ -29,7 +29,7 @@ import { TestLLMServer } from "../lib/llm-server"
// Same layer setup as prompt-effect.test.ts // Same layer setup as prompt-effect.test.ts
import { NodeFileSystem } from "@effect/platform-node" import { NodeFileSystem } from "@effect/platform-node"
import { Agent as AgentSvc } from "../../src/agent/agent" import { Agent as AgentSvc } from "../../src/agent"
import { Bus } from "../../src/bus" import { Bus } from "../../src/bus"
import { Command } from "../../src/command" import { Command } from "../../src/command"
import { Config } from "../../src/config" import { Config } from "../../src/config"

View file

@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import path from "path" import path from "path"
import { Effect } from "effect" import { Effect } from "effect"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { SystemPrompt } from "../../src/session/system" import { SystemPrompt } from "../../src/session/system"
import { provideInstance, tmpdir } from "../fixture/fixture" import { provideInstance, tmpdir } from "../fixture/fixture"

View file

@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test" import { describe, expect, test } from "bun:test"
import path from "path" import path from "path"
import { Shell } from "../../src/shell/shell" import { Shell } from "../../src/shell"
import { Filesystem } from "../../src/util" import { Filesystem } from "../../src/util"
const withShell = async (shell: string | undefined, fn: () => void | Promise<void>) => { const withShell = async (shell: string | undefined, fn: () => void | Promise<void>) => {

View file

@ -6,7 +6,7 @@ import { Instance } from "../../src/project/instance"
import { SyncEvent } from "../../src/sync" import { SyncEvent } from "../../src/sync"
import { Database } from "../../src/storage" import { Database } from "../../src/storage"
import { EventTable } from "../../src/sync/event.sql" import { EventTable } from "../../src/sync/event.sql"
import { Identifier } from "../../src/id/id" import { Identifier } from "../../src/id"
import { Flag } from "../../src/flag/flag" import { Flag } from "../../src/flag/flag"
import { initProjectors } from "../../src/server/projectors" import { initProjectors } from "../../src/server/projectors"

View file

@ -7,7 +7,7 @@ import { Instance } from "../../src/project/instance"
import { LSP } from "../../src/lsp" import { LSP } from "../../src/lsp"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Format } from "../../src/format" import { Format } from "../../src/format"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Bus } from "../../src/bus" import { Bus } from "../../src/bus"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"

View file

@ -2,13 +2,13 @@ import { describe, expect, test } from "bun:test"
import { Effect, Layer, ManagedRuntime } from "effect" import { Effect, Layer, ManagedRuntime } from "effect"
import os from "os" import os from "os"
import path from "path" import path from "path"
import { Shell } from "../../src/shell/shell" import { Shell } from "../../src/shell"
import { BashTool } from "../../src/tool/bash" import { BashTool } from "../../src/tool/bash"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { Filesystem } from "../../src/util" import { Filesystem } from "../../src/util"
import { tmpdir } from "../fixture/fixture" import { tmpdir } from "../fixture/fixture"
import type { Permission } from "../../src/permission" import type { Permission } from "../../src/permission"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"

View file

@ -9,7 +9,7 @@ import { FileTime } from "../../src/file/time"
import { LSP } from "../../src/lsp" import { LSP } from "../../src/lsp"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Format } from "../../src/format" import { Format } from "../../src/format"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Bus } from "../../src/bus" import { Bus } from "../../src/bus"
import { BusEvent } from "../../src/bus/bus-event" import { BusEvent } from "../../src/bus/bus-event"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"

View file

@ -7,7 +7,7 @@ import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { Ripgrep } from "../../src/file/ripgrep" import { Ripgrep } from "../../src/file/ripgrep"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { provideTmpdirInstance } from "../fixture/fixture" import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"

View file

@ -6,7 +6,7 @@ import { provideInstance, provideTmpdirInstance } from "../fixture/fixture"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Ripgrep } from "../../src/file/ripgrep" import { Ripgrep } from "../../src/file/ripgrep"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { testEffect } from "../lib/effect" import { testEffect } from "../lib/effect"

View file

@ -3,7 +3,7 @@ import { Effect, Fiber, Layer } from "effect"
import { QuestionTool } from "../../src/tool/question" import { QuestionTool } from "../../src/tool/question"
import { Question } from "../../src/question" import { Question } from "../../src/question"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"
import { provideTmpdirInstance } from "../fixture/fixture" import { provideTmpdirInstance } from "../fixture/fixture"

View file

@ -1,7 +1,7 @@
import { afterEach, describe, expect } from "bun:test" import { afterEach, describe, expect } from "bun:test"
import { Cause, Effect, Exit, Layer } from "effect" import { Cause, Effect, Exit, Layer } from "effect"
import path from "path" import path from "path"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { AppFileSystem } from "@opencode-ai/shared/filesystem" import { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { FileTime } from "../../src/file/time" import { FileTime } from "../../src/file/time"

View file

@ -1,6 +1,6 @@
import { afterEach, describe, expect } from "bun:test" import { afterEach, describe, expect } from "bun:test"
import { Effect, Layer } from "effect" import { Effect, Layer } from "effect"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Config } from "../../src/config" import { Config } from "../../src/config"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"

View file

@ -1,7 +1,7 @@
import { describe, test, expect } from "bun:test" import { describe, test, expect } from "bun:test"
import { Effect, Layer, ManagedRuntime } from "effect" import { Effect, Layer, ManagedRuntime } from "effect"
import z from "zod" import z from "zod"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Tool } from "../../src/tool" import { Tool } from "../../src/tool"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"

View file

@ -2,7 +2,7 @@ import { describe, test, expect } from "bun:test"
import { NodeFileSystem } from "@effect/platform-node" import { NodeFileSystem } from "@effect/platform-node"
import { Effect, FileSystem, Layer } from "effect" import { Effect, FileSystem, Layer } from "effect"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"
import { Identifier } from "../../src/id/id" import { Identifier } from "../../src/id"
import { Process } from "../../src/util" import { Process } from "../../src/util"
import { Filesystem } from "../../src/util" import { Filesystem } from "../../src/util"
import path from "path" import path from "path"

View file

@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"
import path from "path" import path from "path"
import { Effect, Layer } from "effect" import { Effect, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http" import { FetchHttpClient } from "effect/unstable/http"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"
import { Instance } from "../../src/project/instance" import { Instance } from "../../src/project/instance"
import { WebFetchTool } from "../../src/tool/webfetch" import { WebFetchTool } from "../../src/tool/webfetch"

View file

@ -11,7 +11,7 @@ import { Bus } from "../../src/bus"
import { Format } from "../../src/format" import { Format } from "../../src/format"
import { Truncate } from "../../src/tool" import { Truncate } from "../../src/tool"
import { Tool } from "../../src/tool" import { Tool } from "../../src/tool"
import { Agent } from "../../src/agent/agent" import { Agent } from "../../src/agent"
import { SessionID, MessageID } from "../../src/session/schema" import { SessionID, MessageID } from "../../src/session/schema"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { provideTmpdirInstance } from "../fixture/fixture" import { provideTmpdirInstance } from "../fixture/fixture"