This commit is contained in:
Dax Raad
2025-08-06 23:15:19 -04:00
parent 323ce486a5
commit d45d5d664c
15 changed files with 71 additions and 43 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ export namespace Agent {
})
export type Info = z.infer<typeof Info>
const state = State.create(
() => Paths.use().directory,
() => Paths.directory,
async () => {
const cfg = await Config.get()
const result: Record<string, Info> = {
+4 -1
View File
@@ -8,6 +8,7 @@ import os from "os"
import { z } from "zod"
import { Project } from "../project/project"
import { Paths } from "../project/path"
import { State } from "../project/state"
export namespace App {
const log = Log.create({ service: "app" })
@@ -100,7 +101,7 @@ export namespace App {
return ctx.provide(app, async () => {
return Project.provide(project, async () => {
return Paths.provide(
const result = await Paths.provide(
{
worktree: app.info.path.root,
directory: app.info.path.cwd,
@@ -118,6 +119,8 @@ export namespace App {
}
},
)
await State.dispose(app.info.path.cwd)
return result
})
})
}
+14 -6
View File
@@ -1,11 +1,22 @@
import { z, type ZodType } from "zod"
import { Log } from "../util/log"
import { State } from "../project/state"
import { Paths } from "../project/path"
export namespace Bus {
const log = Log.create({ service: "bus" })
type Subscription = (event: any) => void
const subscriptions = new Map<any, Subscription[]>()
const state = State.create(
() => Paths.directory,
() => {
const subscriptions = new Map<any, Subscription[]>()
return {
subscriptions,
}
},
)
export type EventDefinition = ReturnType<typeof event>
@@ -30,10 +41,6 @@ export namespace Bus {
.object({
type: z.literal(type),
properties: def.properties,
context: z.object({
projectID: z.string().optional(),
sessionID: z.string().optional(),
}),
})
.openapi({
ref: "Event" + "." + def.type,
@@ -56,7 +63,7 @@ export namespace Bus {
})
const pending = []
for (const key of [def.type, "*"]) {
const match = subscriptions.get(key)
const match = state().subscriptions.get(key)
for (const sub of match ?? []) {
pending.push(sub(payload))
}
@@ -89,6 +96,7 @@ export namespace Bus {
function raw(type: string, callback: (event: any) => void) {
log.info("subscribing", { type })
const subscriptions = state().subscriptions
let match = subscriptions.get(type) ?? []
match.push(callback)
subscriptions.set(type, match)
+5 -6
View File
@@ -20,13 +20,12 @@ export namespace Config {
const log = Log.create({ service: "config" })
export const state = State.create(
() => Paths.use().directory,
() => Paths.directory,
async () => {
const paths = Paths.use()
const auth = await Auth.all()
let result = await global()
for (const file of ["opencode.jsonc", "opencode.json"]) {
const found = await Filesystem.findUp(file, paths.directory, paths.worktree)
const found = await Filesystem.findUp(file, Paths.directory, Paths.worktree)
for (const resolved of found.toReversed()) {
result = mergeDeep(result, await loadFile(resolved))
}
@@ -49,7 +48,7 @@ export namespace Config {
result.agent = result.agent || {}
const markdownAgents = [
...(await Filesystem.globUp("agent/*.md", Global.Path.config, Global.Path.config)),
...(await Filesystem.globUp(".opencode/agent/*.md", paths.directory, paths.worktree)),
...(await Filesystem.globUp(".opencode/agent/*.md", Paths.directory, Paths.worktree)),
]
for (const item of markdownAgents) {
const content = await Bun.file(item).text()
@@ -75,7 +74,7 @@ export namespace Config {
result.mode = result.mode || {}
const markdownModes = [
...(await Filesystem.globUp("mode/*.md", Global.Path.config, Global.Path.config)),
...(await Filesystem.globUp(".opencode/mode/*.md", paths.directory, paths.worktree)),
...(await Filesystem.globUp(".opencode/mode/*.md", Paths.directory, Paths.worktree)),
]
for (const item of markdownModes) {
const content = await Bun.file(item).text()
@@ -101,7 +100,7 @@ export namespace Config {
result.plugin.push(
...[
...(await Filesystem.globUp("plugin/*.ts", Global.Path.config, Global.Path.config)),
...(await Filesystem.globUp(".opencode/plugin/*.ts", paths.directory, paths.worktree)),
...(await Filesystem.globUp(".opencode/plugin/*.ts", Paths.directory, Paths.worktree)),
].map((x) => "file://" + x),
)
+14 -10
View File
@@ -1,18 +1,22 @@
import { App } from "../app/app"
import { Paths } from "../project/path"
import { State } from "../project/state"
import { Log } from "../util/log"
export namespace FileTime {
const log = Log.create({ service: "file.time" })
export const state = App.state("tool.filetimes", () => {
const read: {
[sessionID: string]: {
[path: string]: Date | undefined
export const state = State.create(
() => Paths.directory,
() => {
const read: {
[sessionID: string]: {
[path: string]: Date | undefined
}
} = {}
return {
read,
}
} = {}
return {
read,
}
})
},
)
export function read(sessionID: string, file: string) {
log.info("read", { sessionID, file })
+3 -2
View File
@@ -5,6 +5,7 @@ import { App } from "../app/app"
import { Log } from "../util/log"
import { Flag } from "../flag/flag"
import { Paths } from "../project/path"
import { State } from "../project/state"
export namespace FileWatcher {
const log = Log.create({ service: "file.watcher" })
@@ -18,8 +19,8 @@ export namespace FileWatcher {
}),
),
}
const state = App.state(
() => Paths.use().worktree,
const state = State.create(
() => Paths.directory,
() => {
const app = App.use()
if (!app.info.git) return {}
+1 -1
View File
@@ -14,7 +14,7 @@ export namespace Format {
const log = Log.create({ service: "format" })
const state = State.create(
() => Paths.use().directory,
() => Paths.directory,
async () => {
const enabled: Record<string, boolean> = {}
const cfg = await Config.get()
+1 -1
View File
@@ -56,7 +56,7 @@ export namespace LSP {
export type DocumentSymbol = z.infer<typeof DocumentSymbol>
const state = State.create(
() => Paths.use().worktree,
() => Paths.directory,
async () => {
const clients: LSPClient.Info[] = []
const servers: Record<string, LSPServer.Info> = LSPServer
+2 -2
View File
@@ -2,7 +2,6 @@ import { experimental_createMCPClient, type Tool } from "ai"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
import { App } from "../app/app"
import { Config } from "../config/config"
import { Log } from "../util/log"
import { NamedError } from "../util/error"
@@ -10,6 +9,7 @@ import { z } from "zod"
import { Session } from "../session"
import { Bus } from "../bus"
import { State } from "../project/state"
import { Paths } from "../project/path"
export namespace MCP {
const log = Log.create({ service: "mcp" })
@@ -22,7 +22,7 @@ export namespace MCP {
)
const state = State.create(
"mcp",
() => Paths.directory,
async () => {
const cfg = await Config.get()
const clients: {
+2 -2
View File
@@ -4,7 +4,7 @@ import { Log } from "../util/log"
import { Identifier } from "../id/id"
import { Plugin } from "../plugin"
import { State } from "../project/state"
import { Project } from "../project/project"
import { Paths } from "../project/path"
export namespace Permission {
const log = Log.create({ service: "permission" })
@@ -37,7 +37,7 @@ export namespace Permission {
}
const state = State.create(
() => Project.use().id,
() => Paths.directory,
() => {
const pending: {
[sessionID: string]: {
+1 -1
View File
@@ -3,7 +3,7 @@ import { Context } from "../util/context"
const context = Context.create<{ directory: string; worktree: string }>("path")
export const Paths = {
provider: context.provide,
provide: context.provide,
get directory() {
return context.use().directory
},
+10 -5
View File
@@ -4,15 +4,20 @@ export namespace State {
dispose?: (state: any) => Promise<void>
}
const entries = new Map<string, Entry>()
const entries = new Map<string, Map<any, Entry>>()
export function create<S>(root: () => string, init: () => S, dispose?: (state: Awaited<S>) => Promise<void>) {
return () => {
const key = root()
const exists = entries.get(key)
let collection = entries.get(key)
if (!collection) {
collection = new Map<string, Entry>()
entries.set(key, collection)
}
const exists = collection.get(init)
if (exists) return exists.state as S
const state = init()
entries.set(key, {
collection.set(init, {
state,
dispose,
})
@@ -20,8 +25,8 @@ export namespace State {
}
}
export async function dispose() {
for (const [_, entry] of entries.entries()) {
export async function dispose(key: string) {
for (const [_, entry] of entries.get(key)?.entries() ?? []) {
if (!entry.dispose) continue
await entry.dispose(await entry.state)
}
+3 -3
View File
@@ -1,7 +1,7 @@
import { Bus } from "../bus"
import { Installation } from "../installation"
import { Session } from "../session"
import { Storage } from "../storage/storage"
import { StorageNext } from "../storage/storage-next"
import { Log } from "../util/log"
export namespace Share {
@@ -46,8 +46,8 @@ export namespace Share {
}
export function init() {
Bus.subscribe(Storage.Event.Write, async (payload) => {
await sync(payload.properties.key, payload.properties.content)
Bus.subscribe(StorageNext.Event.Write, async (payload) => {
await sync(payload.properties.key.join("/"), payload.properties.content)
})
}
@@ -4,8 +4,14 @@ import fs from "fs/promises"
import { Global } from "../global"
import { lazy } from "../util/lazy"
import { Lock } from "../util/lock"
import { Bus } from "../bus"
import z from "zod"
export namespace StorageNext {
export const Event = {
Write: Bus.event("storage.write", z.object({ key: z.string().array(), content: z.any() })),
}
const log = Log.create({ service: "storage" })
type Migration = (dir: string) => Promise<void>
@@ -49,6 +55,7 @@ export namespace StorageNext {
const content = await Bun.file(target).json()
fn(content)
await Bun.write(target, JSON.stringify(content, null, 2))
Bus.publish(Event.Write, { key, content })
return content as T
}
@@ -57,6 +64,7 @@ export namespace StorageNext {
const target = path.join(dir, ...key) + ".json"
using _ = await Lock.write("storage")
await Bun.write(target, JSON.stringify(content, null, 2))
Bus.publish(Event.Write, { key, content })
}
const glob = new Bun.Glob("**/*")
+2 -2
View File
@@ -2,7 +2,7 @@ import { z } from "zod"
import { Tool } from "./tool"
import DESCRIPTION_WRITE from "./todowrite.txt"
import { State } from "../project/state"
import { Project } from "../project/project"
import { Paths } from "../project/path"
const TodoInfo = z.object({
content: z.string().describe("Brief description of the task"),
@@ -13,7 +13,7 @@ const TodoInfo = z.object({
type TodoInfo = z.infer<typeof TodoInfo>
const state = State.create(
() => Project.use().id,
() => Paths.directory,
() => {
const todos: {
[sessionId: string]: TodoInfo[]