refactor: simplify upstream mod

This commit is contained in:
Catriel Müller
2026-05-29 07:52:12 -03:00
parent 7e4c297abd
commit 2972acf0f5
12 changed files with 431 additions and 158 deletions
@@ -25,14 +25,4 @@ describe("tui thread", () => {
test("uses the real cwd after resolving a relative project from PWD", async () => {
await check(".")
})
// kilocode_change start
test("ignores stale PWD after cwd is changed by a process wrapper", async () => {
await using root = await tmpdir()
const pkg = path.join(root.path, "packages", "opencode")
await fs.mkdir(pkg, { recursive: true })
expect(resolveThreadDirectory(".", root.path, pkg)).toBe(pkg)
})
// kilocode_change end
})
@@ -0,0 +1,15 @@
import { describe, expect, test } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { tmpdir } from "../../../fixture/fixture"
import { resolveThreadDirectory } from "../../../../src/cli/cmd/tui/thread"
describe("kilo tui thread", () => {
test("ignores stale PWD after cwd is changed by a process wrapper", async () => {
await using root = await tmpdir()
const pkg = path.join(root.path, "packages", "opencode")
await fs.mkdir(pkg, { recursive: true })
expect(resolveThreadDirectory(".", root.path, pkg)).toBe(pkg)
})
})
@@ -0,0 +1,260 @@
import { NodeFileSystem } from "@effect/platform-node"
import { expect } from "bun:test"
import { Effect, Exit, Fiber, Layer } from "effect"
import { FetchHttpClient } from "effect/unstable/http"
import path from "path"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import * as Log from "@opencode-ai/core/util/log"
import { Agent as AgentSvc } from "../../src/agent/agent"
import { Bus } from "../../src/bus"
import { Command } from "../../src/command"
import { Config } from "../../src/config/config"
import { Env } from "../../src/env"
import { Format } from "../../src/format"
import { Git } from "../../src/git"
import { LSP } from "../../src/lsp/lsp"
import { MCP } from "../../src/mcp"
import { Permission } from "../../src/permission"
import { Plugin } from "../../src/plugin"
import { Provider as ProviderSvc } from "../../src/provider/provider"
import { Question } from "../../src/question"
import { SessionCompaction } from "../../src/session/compaction"
import { Instruction } from "../../src/session/instruction"
import { LLM } from "../../src/session/llm"
import { SessionProcessor } from "../../src/session/processor"
import { SessionPrompt } from "../../src/session/prompt"
import { SessionRevert } from "../../src/session/revert"
import { SessionRunState } from "../../src/session/run-state"
import { Session } from "../../src/session/session"
import { SessionStatus } from "../../src/session/status"
import { SystemPrompt } from "../../src/session/system"
import { SessionSummary } from "../../src/session/summary"
import { Todo } from "../../src/session/todo"
import { Skill } from "../../src/skill"
import { Snapshot } from "../../src/snapshot"
import { Ripgrep } from "../../src/file/ripgrep"
import { ToolRegistry } from "../../src/tool/registry"
import { Truncate } from "../../src/tool/truncate"
import { provideTmpdirServer } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { reply, TestLLMServer } from "../lib/llm-server"
void Log.init({ print: false })
const waitFor = <A, E, R>(label: string, run: Effect.Effect<A | undefined, E, R>) =>
Effect.gen(function* () {
const end = Date.now() + 5_000
while (Date.now() < end) {
const result = yield* run
if (result !== undefined) return result
yield* Effect.sleep(20)
}
throw new Error(`timed out waiting for ${label}`)
})
const summary = Layer.succeed(
SessionSummary.Service,
SessionSummary.Service.of({
summarize: () => Effect.void,
diff: () => Effect.succeed([]),
computeDiff: () => Effect.succeed([]),
}),
)
const mcp = Layer.succeed(
MCP.Service,
MCP.Service.of({
status: () => Effect.succeed({}),
clients: () => Effect.succeed({}),
tools: () => Effect.succeed({}),
prompts: () => Effect.succeed({}),
resources: () => Effect.succeed({}),
add: () => Effect.succeed({ status: { status: "disabled" as const } }),
connect: () => Effect.void,
disconnect: () => Effect.void,
getPrompt: () => Effect.succeed(undefined),
readResource: () => Effect.succeed(undefined),
startAuth: () => Effect.die("unexpected MCP auth in permission refresh tests"),
authenticate: () => Effect.die("unexpected MCP auth in permission refresh tests"),
finishAuth: () => Effect.die("unexpected MCP auth in permission refresh tests"),
removeAuth: () => Effect.void,
supportsOAuth: () => Effect.succeed(false),
hasStoredTokens: () => Effect.succeed(false),
getAuthStatus: () => Effect.succeed("not_authenticated" as const),
}),
)
const lsp = Layer.succeed(
LSP.Service,
LSP.Service.of({
init: () => Effect.void,
status: () => Effect.succeed([]),
hasClients: () => Effect.succeed(false),
touchFile: () => Effect.void,
diagnostics: () => Effect.succeed({}),
hover: () => Effect.succeed(undefined),
definition: () => Effect.succeed([]),
references: () => Effect.succeed([]),
implementation: () => Effect.succeed([]),
documentSymbol: () => Effect.succeed([]),
workspaceSymbol: () => Effect.succeed([]),
prepareCallHierarchy: () => Effect.succeed([]),
incomingCalls: () => Effect.succeed([]),
outgoingCalls: () => Effect.succeed([]),
}),
)
const status = SessionStatus.layer.pipe(Layer.provideMerge(Bus.layer))
const run = SessionRunState.layer.pipe(Layer.provide(status))
const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer)
function makeHttp() {
const deps = Layer.mergeAll(
Session.defaultLayer,
Snapshot.defaultLayer,
LLM.defaultLayer,
Env.defaultLayer,
AgentSvc.defaultLayer,
Command.defaultLayer,
Permission.defaultLayer,
Plugin.defaultLayer,
Config.defaultLayer,
ProviderSvc.defaultLayer,
lsp,
mcp,
AppFileSystem.defaultLayer,
status,
).pipe(Layer.provideMerge(infra))
const question = Question.layer.pipe(Layer.provideMerge(deps))
const todo = Todo.layer.pipe(Layer.provideMerge(deps))
const registry = ToolRegistry.layer.pipe(
Layer.provide(Skill.defaultLayer),
Layer.provide(FetchHttpClient.layer),
Layer.provide(CrossSpawnSpawner.defaultLayer),
Layer.provide(Ripgrep.defaultLayer),
Layer.provide(Format.defaultLayer),
Layer.provide(Git.defaultLayer),
Layer.provideMerge(todo),
Layer.provideMerge(question),
Layer.provideMerge(deps),
)
const trunc = Truncate.layer.pipe(Layer.provideMerge(deps))
const proc = SessionProcessor.layer.pipe(Layer.provide(summary), Layer.provideMerge(deps))
const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps))
return Layer.mergeAll(
TestLLMServer.layer,
SessionPrompt.layer.pipe(
Layer.provide(SessionRevert.defaultLayer),
Layer.provide(summary),
Layer.provideMerge(run),
Layer.provideMerge(compact),
Layer.provideMerge(proc),
Layer.provideMerge(registry),
Layer.provideMerge(trunc),
Layer.provide(Instruction.defaultLayer),
Layer.provide(SystemPrompt.defaultLayer),
Layer.provideMerge(deps),
),
).pipe(Layer.provide(summary))
}
const it = testEffect(makeHttp())
const cfg = {
provider: {
test: {
name: "Test",
id: "test",
env: [],
npm: "@ai-sdk/openai-compatible",
models: {
"test-model": {
id: "test-model",
name: "Test Model",
attachment: false,
reasoning: false,
temperature: false,
tool_call: true,
release_date: "2025-01-01",
limit: { context: 100000, output: 10000 },
cost: { input: 0, output: 0 },
options: {},
},
},
options: {
apiKey: "test-key",
baseURL: "http://localhost:1/v1",
},
},
},
}
function providerCfg(url: string) {
return {
...cfg,
provider: {
...cfg.provider,
test: {
...cfg.provider.test,
options: {
...cfg.provider.test.options,
baseURL: url,
},
},
},
}
}
it.live("active tool calls use permissions changed after model streaming starts", () =>
provideTmpdirServer(
Effect.fnUntraced(function* ({ dir, llm }) {
const config = yield* Config.Service
const prompt = yield* SessionPrompt.Service
const sessions = yield* Session.Service
const permission = yield* Permission.Service
const file = path.join(dir, "note.txt")
const gate = Promise.withResolvers<void>()
yield* Effect.promise(() => Bun.write(file, "old"))
yield* llm.push(
reply()
.wait(gate.promise)
.tool("edit", { filePath: file, oldString: "old", newString: "new" }),
)
const chat = yield* sessions.create({ title: "Pinned" })
yield* prompt.prompt({
sessionID: chat.id,
agent: "build",
noReply: true,
parts: [{ type: "text", text: "edit note" }],
})
const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkScoped)
yield* llm.wait(1)
yield* config.update({ permission: { edit: { "*": "allow" } } } as Config.Info)
gate.resolve(undefined)
yield* waitFor(
"edit without permission prompt",
Effect.gen(function* () {
const pending = yield* permission.list()
if (pending.length) throw new Error("edit permission was requested after config allowed it")
const text = yield* Effect.promise(() => Bun.file(file).text())
if (text === "new") return text
}),
)
const exit = yield* Fiber.await(fiber)
expect(Exit.isSuccess(exit)).toBe(true)
}),
{
git: true,
config: (url) => ({
...providerCfg(url),
permission: { edit: "ask" },
}),
},
),
)
@@ -395,62 +395,6 @@ it.live("loop calls LLM and returns assistant message", () =>
{ git: true, config: providerCfg },
),
)
// kilocode_change start - active tools must re-read permissions after config changes
it.live("active tool calls use permissions changed after model streaming starts", () =>
provideTmpdirServer(
Effect.fnUntraced(function* ({ dir, llm }) {
const config = yield* Config.Service
const prompt = yield* SessionPrompt.Service
const sessions = yield* Session.Service
const permission = yield* Permission.Service
const file = path.join(dir, "note.txt")
const gate = defer<void>()
yield* Effect.promise(() => Bun.write(file, "old"))
yield* llm.push(
reply()
.wait(gate.promise)
.tool("edit", { filePath: file, oldString: "old", newString: "new" }),
)
const chat = yield* sessions.create({ title: "Pinned" })
yield* prompt.prompt({
sessionID: chat.id,
agent: "build",
noReply: true,
parts: [{ type: "text", text: "edit note" }],
})
const fiber = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkScoped)
yield* llm.wait(1)
yield* config.update({ permission: { edit: { "*": "allow" } } } as Config.Info)
gate.resolve(undefined)
yield* waitFor(
"edit without permission prompt",
Effect.gen(function* () {
const pending = yield* permission.list()
if (pending.length) throw new Error("edit permission was requested after config allowed it")
const text = yield* Effect.promise(() => Bun.file(file).text())
if (text === "new") return text
}),
)
const exit = yield* Fiber.await(fiber)
expect(Exit.isSuccess(exit)).toBe(true)
}),
{
git: true,
config: (url) => ({
...providerCfg(url),
permission: { edit: "ask" },
}),
},
),
)
// kilocode_change end
it.live("prompt emits v2 prompted and synthetic events", () =>
provideTmpdirServer(
Effect.fnUntraced(function* () {