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