progress
This commit is contained in:
@@ -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> = {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,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: {
|
||||
|
||||
@@ -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]: {
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,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[]
|
||||
|
||||
Reference in New Issue
Block a user