mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
fix(opencode): complete v1.17.4 compatibility
This commit is contained in:
@@ -12,6 +12,7 @@ import { FSUtil } from "./fs-util"
|
||||
import { Global } from "./global"
|
||||
import { DataMigrationTable } from "./data-migration.sql"
|
||||
import path from "path"
|
||||
import { parse as parseKiloAccounts } from "./kilocode/credential-migration" // kilocode_change
|
||||
|
||||
export const ID = Schema.String.pipe(
|
||||
Schema.brand("Credential.ID"),
|
||||
@@ -106,6 +107,62 @@ export const legacyImportLayer = Layer.effectDiscard(
|
||||
const { db } = yield* Database.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
const global = yield* Global.Service
|
||||
// kilocode_change start - preserve Kilo's multi-account JSON stores before the upstream auth.json fallback
|
||||
const kiloName = "credential.kilo-account-json"
|
||||
if (!(yield* db.select().from(DataMigrationTable).where(eq(DataMigrationTable.name, kiloName)).get())) {
|
||||
const current = yield* fs.readJson(path.join(global.data, "account.json")).pipe(Effect.option)
|
||||
const prior = yield* fs.readJson(path.join(global.data, "auth-v2.json")).pipe(Effect.option)
|
||||
const raw = Option.isSome(current) ? current.value : Option.getOrUndefined(prior)
|
||||
const values = parseKiloAccounts(raw)
|
||||
if (values.length > 0) {
|
||||
yield* db.transaction((tx) =>
|
||||
Effect.gen(function* () {
|
||||
const existing = new Set(
|
||||
(yield* tx.select({ connectorID: CredentialTable.connector_id }).from(CredentialTable).all()).map(
|
||||
(item) => item.connectorID,
|
||||
),
|
||||
)
|
||||
for (const item of values) {
|
||||
const connector = ConnectorSchema.ID.make(item.connectorID.replace(/\/+$/, ""))
|
||||
if (existing.has(connector)) continue
|
||||
const value: Value =
|
||||
item.credential.type === "api"
|
||||
? new Key({
|
||||
type: "key",
|
||||
key: item.credential.key,
|
||||
metadata: item.credential.metadata,
|
||||
})
|
||||
: new OAuth({
|
||||
type: "oauth",
|
||||
refresh: item.credential.refresh,
|
||||
access: item.credential.access,
|
||||
expires: item.credential.expires,
|
||||
metadata: {
|
||||
...(item.credential.accountId ? { accountID: item.credential.accountId } : {}),
|
||||
...(item.credential.enterpriseUrl ? { enterpriseURL: item.credential.enterpriseUrl } : {}),
|
||||
},
|
||||
})
|
||||
yield* tx.insert(CredentialTable).values({
|
||||
id: ID.create(),
|
||||
connector_id: connector,
|
||||
method_id: ConnectorSchema.MethodID.make(
|
||||
item.credential.type === "api"
|
||||
? "api-key"
|
||||
: connector === ConnectorSchema.ID.make("openai")
|
||||
? "chatgpt-browser"
|
||||
: "oauth",
|
||||
),
|
||||
label: item.label,
|
||||
value,
|
||||
active: item.active,
|
||||
})
|
||||
}
|
||||
yield* tx.insert(DataMigrationTable).values({ name: kiloName, time_completed: Date.now() }).run()
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
// kilocode_change end
|
||||
const name = "credential.auth-json"
|
||||
if (yield* db.select().from(DataMigrationTable).where(eq(DataMigrationTable.name, name)).get()) return
|
||||
const raw = yield* fs.readJson(path.join(global.data, "auth.json")).pipe(Effect.option)
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Option, Schema } from "effect"
|
||||
import { NonNegativeInt } from "../schema"
|
||||
|
||||
const OAuth = Schema.Struct({
|
||||
type: Schema.Literal("oauth"),
|
||||
refresh: Schema.String,
|
||||
access: Schema.String,
|
||||
expires: NonNegativeInt,
|
||||
accountId: Schema.optional(Schema.String),
|
||||
enterpriseUrl: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
const Key = Schema.Struct({
|
||||
type: Schema.Literal("api"),
|
||||
key: Schema.String,
|
||||
metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
||||
})
|
||||
|
||||
const Account = Schema.Struct({
|
||||
id: Schema.String,
|
||||
serviceID: Schema.String,
|
||||
description: Schema.String,
|
||||
credential: Schema.Union([OAuth, Key]),
|
||||
})
|
||||
|
||||
const Store = Schema.Struct({
|
||||
version: Schema.Literal(2),
|
||||
accounts: Schema.Record(Schema.String, Account),
|
||||
active: Schema.Record(Schema.String, Schema.String),
|
||||
})
|
||||
|
||||
export function parse(input: unknown) {
|
||||
const decoded = Schema.decodeUnknownOption(Store)(input)
|
||||
if (Option.isNone(decoded)) return []
|
||||
const first = new Set<string>()
|
||||
return Object.values(decoded.value.accounts).map((account) => {
|
||||
const fallback = !first.has(account.serviceID)
|
||||
first.add(account.serviceID)
|
||||
return {
|
||||
connectorID: account.serviceID,
|
||||
label: account.description,
|
||||
credential: account.credential,
|
||||
active: decoded.value.active[account.serviceID]
|
||||
? decoded.value.active[account.serviceID] === account.id
|
||||
: fallback,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
export * as Log from "./log"
|
||||
|
||||
import path from "path"
|
||||
import { existsSync, writeFileSync } from "fs" // kilocode_change
|
||||
import fs from "fs/promises"
|
||||
import * as Global from "../global"
|
||||
import { Schema } from "effect"
|
||||
import { Glob } from "./glob"
|
||||
import { createStream } from "rotating-file-stream" // kilocode_change
|
||||
import { KILO_RUN_ID } from "./opencode-process" // kilocode_change
|
||||
|
||||
export const Level = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({
|
||||
identifier: "LogLevel",
|
||||
description: "Log level",
|
||||
})
|
||||
export type Level = Schema.Schema.Type<typeof Level>
|
||||
|
||||
const levelPriority: Record<Level, number> = {
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
}
|
||||
const keep = 10
|
||||
const initializedRunID = "KILO_LOG_INITIALIZED_RUN_ID"
|
||||
|
||||
let level: Level = "INFO"
|
||||
|
||||
function shouldLog(input: Level): boolean {
|
||||
return levelPriority[input] >= levelPriority[level]
|
||||
}
|
||||
|
||||
export type Logger = {
|
||||
debug(message?: any, extra?: Record<string, any>): void
|
||||
info(message?: any, extra?: Record<string, any>): void
|
||||
error(message?: any, extra?: Record<string, any>): void
|
||||
warn(message?: any, extra?: Record<string, any>): void
|
||||
tag(key: string, value: string): Logger
|
||||
clone(): Logger
|
||||
time(
|
||||
message: string,
|
||||
extra?: Record<string, any>,
|
||||
): {
|
||||
stop(): void
|
||||
[Symbol.dispose](): void
|
||||
}
|
||||
}
|
||||
|
||||
const loggers = new Map<string, Logger>()
|
||||
|
||||
export const Default = create({ service: "default" })
|
||||
|
||||
export interface Options {
|
||||
print: boolean
|
||||
dev?: boolean
|
||||
level?: Level
|
||||
}
|
||||
|
||||
let logpath = ""
|
||||
export function file() {
|
||||
return logpath
|
||||
}
|
||||
const stderr = (msg: any) => {
|
||||
process.stderr.write(msg)
|
||||
return msg.length
|
||||
}
|
||||
let write = stderr
|
||||
let stream: ReturnType<typeof createStream> | undefined // kilocode_change
|
||||
|
||||
export async function init(options: Options) {
|
||||
if (options.level) level = options.level
|
||||
void cleanup(Global.Path.log)
|
||||
// kilocode_change start - initialize one rotating stream and truncate dev.log once per Kilo run
|
||||
if (stream) {
|
||||
const active = stream
|
||||
stream = undefined
|
||||
await new Promise<void>((resolve) => active.end(resolve))
|
||||
}
|
||||
if (options.print) {
|
||||
write = stderr
|
||||
return
|
||||
}
|
||||
logpath = path.join(
|
||||
Global.Path.log,
|
||||
options.dev ? "dev.log" : new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
|
||||
)
|
||||
const run = process.env[KILO_RUN_ID]
|
||||
if (!options.dev || !run || process.env[initializedRunID] !== run) {
|
||||
await fs.truncate(logpath).catch(() => {})
|
||||
if (options.dev && run) process.env[initializedRunID] = run
|
||||
}
|
||||
const dir = path.dirname(logpath)
|
||||
const active = createStream(path.basename(logpath), {
|
||||
size: "50M",
|
||||
maxFiles: 10,
|
||||
history: ".log-history",
|
||||
path: dir,
|
||||
})
|
||||
stream = active
|
||||
active.on("rotation", () => {
|
||||
if (!existsSync(dir)) return
|
||||
|
||||
try {
|
||||
// RATIONALE: If current log path was deleted while stream still holds the fd,
|
||||
// rotating-file-stream will try to rename a missing path and emit ENOENT.
|
||||
writeFileSync(logpath, "", { flag: "wx" })
|
||||
} catch (err) {
|
||||
if (typeof err === "object" && err && "code" in err && err.code === "EEXIST") return
|
||||
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
process.stderr.write("log stream warning: " + msg + "\n")
|
||||
}
|
||||
})
|
||||
active.on("error", (err: Error) => {
|
||||
process.stderr.write("log stream error: " + err.message + "\n")
|
||||
})
|
||||
active.on("warning", (err: Error) => {
|
||||
process.stderr.write("log stream warning: " + err.message + "\n")
|
||||
})
|
||||
write = (msg: any) => {
|
||||
active.write(msg)
|
||||
return msg.length
|
||||
}
|
||||
// kilocode_change end
|
||||
}
|
||||
|
||||
async function cleanup(dir: string) {
|
||||
const files = (
|
||||
await Glob.scan("????-??-??T??????.log", {
|
||||
cwd: dir,
|
||||
absolute: false,
|
||||
include: "file",
|
||||
}).catch(() => [])
|
||||
)
|
||||
.filter((file) => path.basename(file) === file)
|
||||
.sort()
|
||||
if (files.length <= keep) return
|
||||
|
||||
const doomed = files.slice(0, -keep)
|
||||
await Promise.all(doomed.map((file) => fs.unlink(path.join(dir, file)).catch(() => {})))
|
||||
}
|
||||
|
||||
function formatError(error: Error, depth = 0): string {
|
||||
const result = error.message
|
||||
return error.cause instanceof Error && depth < 10
|
||||
? result + " Caused by: " + formatError(error.cause, depth + 1)
|
||||
: result
|
||||
}
|
||||
|
||||
let last = Date.now()
|
||||
export function create(tags?: Record<string, any>) {
|
||||
tags = tags || {}
|
||||
|
||||
const service = tags["service"]
|
||||
if (service && typeof service === "string") {
|
||||
const cached = loggers.get(service)
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
}
|
||||
|
||||
function build(message: any, extra?: Record<string, any>) {
|
||||
const prefix = Object.entries({
|
||||
...tags,
|
||||
...extra,
|
||||
})
|
||||
.filter(([_, value]) => value !== undefined && value !== null)
|
||||
.map(([key, value]) => {
|
||||
const prefix = `${key}=`
|
||||
if (value instanceof Error) return prefix + formatError(value)
|
||||
if (typeof value === "object") return prefix + JSON.stringify(value)
|
||||
return prefix + value
|
||||
})
|
||||
.join(" ")
|
||||
const next = new Date()
|
||||
const diff = next.getTime() - last
|
||||
last = next.getTime()
|
||||
return [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message].filter(Boolean).join(" ") + "\n"
|
||||
}
|
||||
const result: Logger = {
|
||||
debug(message?: any, extra?: Record<string, any>) {
|
||||
if (shouldLog("DEBUG")) {
|
||||
write("DEBUG " + build(message, extra))
|
||||
}
|
||||
},
|
||||
info(message?: any, extra?: Record<string, any>) {
|
||||
if (shouldLog("INFO")) {
|
||||
write("INFO " + build(message, extra))
|
||||
}
|
||||
},
|
||||
error(message?: any, extra?: Record<string, any>) {
|
||||
if (shouldLog("ERROR")) {
|
||||
write("ERROR " + build(message, extra))
|
||||
}
|
||||
},
|
||||
warn(message?: any, extra?: Record<string, any>) {
|
||||
if (shouldLog("WARN")) {
|
||||
write("WARN " + build(message, extra))
|
||||
}
|
||||
},
|
||||
tag(key: string, value: string) {
|
||||
if (tags) tags[key] = value
|
||||
return result
|
||||
},
|
||||
clone() {
|
||||
return create({ ...tags })
|
||||
},
|
||||
time(message: string, extra?: Record<string, any>) {
|
||||
const now = Date.now()
|
||||
result.info(message, { status: "started", ...extra })
|
||||
function stop() {
|
||||
result.info(message, {
|
||||
status: "completed",
|
||||
duration: Date.now() - now,
|
||||
...extra,
|
||||
})
|
||||
}
|
||||
return {
|
||||
stop,
|
||||
[Symbol.dispose]() {
|
||||
stop()
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if (service && typeof service === "string") {
|
||||
loggers.set(service, result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export const KILO_RUN_ID = "KILO_RUN_ID"
|
||||
export const KILO_PROCESS_ROLE = "KILO_PROCESS_ROLE"
|
||||
|
||||
export function ensureRunID() {
|
||||
return (process.env[KILO_RUN_ID] ??= crypto.randomUUID())
|
||||
}
|
||||
|
||||
export function ensureProcessRole(fallback: "main" | "worker") {
|
||||
return (process.env[KILO_PROCESS_ROLE] ??= fallback)
|
||||
}
|
||||
|
||||
export function ensureProcessMetadata(fallback: "main" | "worker") {
|
||||
return {
|
||||
runID: ensureRunID(),
|
||||
processRole: ensureProcessRole(fallback),
|
||||
}
|
||||
}
|
||||
|
||||
export function sanitizedProcessEnv(overrides?: Record<string, string>) {
|
||||
const env = Object.fromEntries(
|
||||
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
||||
)
|
||||
return overrides ? Object.assign(env, overrides) : env
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import path from "path"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { Auth } from "@opencode-ai/core/auth"
|
||||
import { Connector } from "@opencode-ai/core/connector"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
@@ -9,11 +11,17 @@ import { tmpdir } from "../fixture/tmpdir"
|
||||
import { it } from "../lib/effect"
|
||||
|
||||
function layer(dir: string) {
|
||||
return Auth.layer.pipe(
|
||||
const database = Database.layerFromPath(path.join(dir, "credential.db")).pipe(Layer.fresh)
|
||||
const importer = Credential.legacyImportLayer.pipe(
|
||||
Layer.provide(database),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provideMerge(EventV2.defaultLayer),
|
||||
Layer.provide(Global.layerWith({ data: dir })),
|
||||
)
|
||||
return Credential.layer.pipe(
|
||||
Layer.provide(database),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provideMerge(importer),
|
||||
)
|
||||
}
|
||||
|
||||
const auth = Effect.acquireRelease(
|
||||
@@ -29,7 +37,7 @@ const auth = Effect.acquireRelease(
|
||||
}),
|
||||
)
|
||||
|
||||
describe("Auth auth-v2 migration", () => {
|
||||
describe("Credential auth-v2 migration", () => {
|
||||
it.live("preserves multiple accounts, active selection, and Kilo organization", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
@@ -72,22 +80,20 @@ describe("Auth auth-v2 migration", () => {
|
||||
yield* Effect.promise(() => Bun.write(path.join(tmp.path, "auth-v2.json"), JSON.stringify(store)))
|
||||
|
||||
const result = yield* Effect.gen(function* () {
|
||||
const accounts = yield* Auth.Service
|
||||
const credentials = yield* Credential.Service
|
||||
return {
|
||||
all: yield* accounts.all(),
|
||||
active: yield* accounts.active(Auth.ServiceID.make("kilo")),
|
||||
all: yield* credentials.all(),
|
||||
active: yield* credentials.active(Connector.ID.make("kilo")),
|
||||
}
|
||||
}).pipe(Effect.provide(layer(tmp.path)))
|
||||
|
||||
expect(result.all.map((item) => String(item.id))).toEqual(["acc_first", "acc_second"])
|
||||
expect(String(result.active?.id)).toBe("acc_second")
|
||||
expect(result.active?.credential.type).toBe("oauth")
|
||||
if (result.active?.credential.type === "oauth") {
|
||||
expect(result.active.credential.access).toBe("access-second")
|
||||
expect(result.active.credential.accountId).toBe("org-second")
|
||||
expect(result.all.map((item) => item.label)).toEqual(["first", "second"])
|
||||
expect(result.active?.label).toBe("second")
|
||||
expect(result.active?.value.type).toBe("oauth")
|
||||
if (result.active?.value.type === "oauth") {
|
||||
expect(result.active.value.access).toBe("access-second")
|
||||
expect(result.active.value.metadata?.accountID).toBe("org-second")
|
||||
}
|
||||
const saved = yield* Effect.promise(() => Bun.file(path.join(tmp.path, "account.json")).json())
|
||||
expect(saved).toEqual(store)
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||
import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
|
||||
import { KiloPlugin } from "@opencode-ai/core/plugin/provider/kilo"
|
||||
@@ -150,7 +151,7 @@ describe("KiloPlugin", () => {
|
||||
const transform = yield* catalog.transform()
|
||||
yield* transform((catalog) => {
|
||||
const item = provider("kilo", {
|
||||
enabled: { via: "account", service: "kilo" },
|
||||
enabled: { via: "credential", credentialID: Credential.ID.make("cred_kilo") },
|
||||
request: {
|
||||
headers: {},
|
||||
body: { apiKey: "authenticated-token", kilocodeOrganizationId: "authenticated-org" },
|
||||
@@ -163,7 +164,7 @@ describe("KiloPlugin", () => {
|
||||
})
|
||||
const result = yield* catalog.provider.get(ProviderV2.ID.make("kilo"))
|
||||
|
||||
expect(result.enabled).toEqual({ via: "account", service: "kilo" })
|
||||
expect(result.enabled).toEqual({ via: "credential", credentialID: Credential.ID.make("cred_kilo") })
|
||||
expect(result.request.body.kilocodeToken).toBe("authenticated-token")
|
||||
expect(result.request.body.kilocodeOrganizationId).toBe("environment-org")
|
||||
}),
|
||||
|
||||
+13
-30
@@ -29,7 +29,7 @@ Options:
|
||||
manage MCP (Model Context Protocol) servers
|
||||
|
||||
Commands:
|
||||
kilo mcp add add an MCP server
|
||||
kilo mcp add [name] add an MCP server
|
||||
kilo mcp list list MCP servers and their status [aliases: ls]
|
||||
kilo mcp auth [name] authenticate with an OAuth-enabled MCP server
|
||||
kilo mcp logout [name] remove OAuth credentials for an MCP server
|
||||
@@ -45,9 +45,15 @@ Options:
|
||||
```
|
||||
add an MCP server
|
||||
|
||||
Positionals:
|
||||
name name of the MCP server [string]
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
--url URL for a remote MCP server [string]
|
||||
--env environment variable for a local MCP server (KEY=VALUE) [array]
|
||||
--header HTTP header for a remote MCP server (KEY=VALUE) [array]
|
||||
```
|
||||
|
||||
### kilo mcp list
|
||||
@@ -289,7 +295,6 @@ Options:
|
||||
ripgrep debugging utilities
|
||||
|
||||
Commands:
|
||||
kilo debug rg tree show file tree using ripgrep
|
||||
kilo debug rg files list files using ripgrep
|
||||
kilo debug rg search <pattern> search file contents using ripgrep
|
||||
|
||||
@@ -298,17 +303,6 @@ Options:
|
||||
--version Show version number [boolean]
|
||||
```
|
||||
|
||||
### kilo debug rg tree
|
||||
|
||||
```
|
||||
show file tree using ripgrep
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
--limit [number]
|
||||
```
|
||||
|
||||
### kilo debug rg files
|
||||
|
||||
```
|
||||
@@ -346,7 +340,6 @@ Commands:
|
||||
kilo debug file read <path> read file contents as JSON
|
||||
kilo debug file list <path> list files in a directory
|
||||
kilo debug file search <query> search files by query
|
||||
kilo debug file tree [dir] show directory tree
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
@@ -392,19 +385,6 @@ Options:
|
||||
--version Show version number [boolean]
|
||||
```
|
||||
|
||||
### kilo debug file tree
|
||||
|
||||
```
|
||||
show directory tree
|
||||
|
||||
Positionals:
|
||||
dir Directory to tree [string] [default: "."]
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
```
|
||||
|
||||
### kilo debug scrap
|
||||
|
||||
```
|
||||
@@ -547,9 +527,9 @@ Options:
|
||||
manage AI providers and credentials
|
||||
|
||||
Commands:
|
||||
kilo auth list list providers and credentials [aliases: ls]
|
||||
kilo auth login [url] log in to a provider
|
||||
kilo auth logout log out from a configured provider
|
||||
kilo auth list list providers and credentials [aliases: ls]
|
||||
kilo auth login [url] log in to a provider
|
||||
kilo auth logout [provider] log out from a configured provider
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
@@ -586,6 +566,9 @@ Options:
|
||||
```
|
||||
log out from a configured provider
|
||||
|
||||
Positionals:
|
||||
provider provider id or name to log out from [string]
|
||||
|
||||
Options:
|
||||
--help Show help [boolean]
|
||||
--version Show version number [boolean]
|
||||
|
||||
@@ -29,9 +29,8 @@ import * as OtelTracer from "@effect/opentelemetry/Tracer"
|
||||
import { AbsolutePath, type DeepMutable } from "@opencode-ai/core/schema"
|
||||
import * as KiloAgent from "@/kilocode/agent" // kilocode_change
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { Reference as ReferenceV1 } from "@/reference/reference" // kilocode_change
|
||||
import { ConfigReference } from "@/config/reference" // kilocode_change
|
||||
import * as AgentRequirements from "@/kilocode/agent-requirements" // kilocode_change
|
||||
import * as KiloReference from "@/kilocode/reference" // kilocode_change
|
||||
import { MCP } from "@/mcp" // kilocode_change
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
@@ -114,398 +113,398 @@ export const layer = Layer.effect(
|
||||
const locations = yield* LocationServiceMap
|
||||
|
||||
const state = yield* InstanceState.make<State>(
|
||||
Effect.fn("Agent.state")(function* (ctx) {
|
||||
const cfg = yield* config.get()
|
||||
const skillDirs = yield* skill.dirs()
|
||||
// kilocode_change start - include global config dirs so agents can read them without prompting
|
||||
const referenceDirs = yield* Effect.gen(function* () {
|
||||
Effect.fn("Agent.state")(function* (ctx) {
|
||||
const cfg = yield* config.get()
|
||||
const skillDirs = yield* skill.dirs()
|
||||
// kilocode_change start - include global config dirs so agents can read them without prompting
|
||||
const referenceDirs = yield* Effect.gen(function* () {
|
||||
yield* (yield* PluginBoot.Service).wait()
|
||||
return (yield* (yield* Reference.Service).list()).map((reference) => reference.path)
|
||||
}).pipe(Effect.provide(locations.get(Location.Ref.make({ directory: AbsolutePath.make(ctx.directory) }))))
|
||||
const whitelistedDirs = [
|
||||
Truncate.GLOB,
|
||||
path.join(Global.Path.tmp, "*"),
|
||||
...skillDirs.map((dir) => path.join(dir, "*")),
|
||||
path.join(Global.Path.config, "*"),
|
||||
Truncate.GLOB,
|
||||
path.join(Global.Path.tmp, "*"),
|
||||
...skillDirs.map((dir) => path.join(dir, "*")),
|
||||
path.join(Global.Path.config, "*"),
|
||||
...KilocodePaths.globalDirs().map((dir) => path.join(dir, "*")),
|
||||
...referenceDirs.map((dir) => path.join(dir, "*")),
|
||||
...referenceDirs.map((dir) => path.join(dir, "*")),
|
||||
]
|
||||
// kilocode_change end
|
||||
const readonlyExternalDirectory = {
|
||||
"*": "ask",
|
||||
...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
||||
} satisfies Record<string, "allow" | "ask" | "deny">
|
||||
|
||||
const baseDefaults = Permission.fromConfig({
|
||||
// kilocode_change
|
||||
"*": "allow",
|
||||
doom_loop: "ask",
|
||||
external_directory: {
|
||||
// kilocode_change end
|
||||
const readonlyExternalDirectory = {
|
||||
"*": "ask",
|
||||
...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
||||
},
|
||||
suggest: "deny", // kilocode_change
|
||||
question: "deny",
|
||||
interactive_terminal: "deny", // kilocode_change - human-driven tools are primary-agent only
|
||||
plan_enter: "deny",
|
||||
plan_exit: "deny",
|
||||
repo_clone: "deny", // kilocode_change
|
||||
repo_overview: "deny", // kilocode_change
|
||||
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
|
||||
read: {
|
||||
} satisfies Record<string, "allow" | "ask" | "deny">
|
||||
|
||||
const baseDefaults = Permission.fromConfig({
|
||||
// kilocode_change
|
||||
"*": "allow",
|
||||
"*.env": "ask",
|
||||
"*.env.*": "ask",
|
||||
"*.env.example": "allow",
|
||||
},
|
||||
})
|
||||
doom_loop: "ask",
|
||||
external_directory: {
|
||||
"*": "ask",
|
||||
...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
||||
},
|
||||
suggest: "deny", // kilocode_change
|
||||
question: "deny",
|
||||
interactive_terminal: "deny", // kilocode_change - human-driven tools are primary-agent only
|
||||
plan_enter: "deny",
|
||||
plan_exit: "deny",
|
||||
repo_clone: "deny", // kilocode_change
|
||||
repo_overview: "deny", // kilocode_change
|
||||
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
|
||||
read: {
|
||||
"*": "allow",
|
||||
"*.env": "ask",
|
||||
"*.env.*": "ask",
|
||||
"*.env.example": "allow",
|
||||
},
|
||||
})
|
||||
|
||||
// kilocode_change start - patch defaults with bash allowlist and recall permission
|
||||
const kilo = KiloAgent.prepare(cfg)
|
||||
const defaults = Permission.merge(baseDefaults, kilo.defaultsPatch)
|
||||
// kilocode_change end
|
||||
|
||||
const user = Permission.fromConfig(cfg.permission ?? {})
|
||||
|
||||
const agents: Record<string, Info> = {
|
||||
build: {
|
||||
name: "build",
|
||||
description: "The default agent. Executes tools based on configured permissions.",
|
||||
options: {},
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
question: "allow",
|
||||
interactive_terminal: "allow", // kilocode_change
|
||||
suggest: "allow", // kilocode_change
|
||||
plan_enter: "allow",
|
||||
}),
|
||||
user,
|
||||
),
|
||||
mode: "primary",
|
||||
native: true,
|
||||
},
|
||||
plan: {
|
||||
name: "plan",
|
||||
description: "Plan mode. Disallows all edit tools.",
|
||||
options: {},
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
question: "allow",
|
||||
plan_exit: "allow",
|
||||
task: {
|
||||
general: "deny",
|
||||
},
|
||||
external_directory: {
|
||||
[path.join(Global.Path.data, "plans", "*")]: "allow",
|
||||
},
|
||||
edit: {
|
||||
"*": "deny",
|
||||
[path.join(".opencode", "plans", "*.md")]: "allow",
|
||||
[path.relative(ctx.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
|
||||
},
|
||||
}),
|
||||
user,
|
||||
),
|
||||
mode: "primary",
|
||||
native: true,
|
||||
},
|
||||
general: {
|
||||
name: "general",
|
||||
description: `General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel.`,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
todowrite: "deny",
|
||||
}),
|
||||
user,
|
||||
),
|
||||
options: {},
|
||||
mode: "subagent",
|
||||
native: true,
|
||||
},
|
||||
explore: {
|
||||
name: "explore",
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
grep: "allow",
|
||||
glob: "allow",
|
||||
list: "allow",
|
||||
bash: "allow",
|
||||
webfetch: "allow",
|
||||
websearch: "allow",
|
||||
read: "allow",
|
||||
external_directory: readonlyExternalDirectory,
|
||||
}),
|
||||
user,
|
||||
),
|
||||
description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
|
||||
prompt: PROMPT_EXPLORE,
|
||||
options: {},
|
||||
mode: "subagent",
|
||||
native: true,
|
||||
},
|
||||
// kilocode_change start - retain Kilo's opt-in repository research agent
|
||||
...(flags.experimentalScout
|
||||
? {
|
||||
scout: {
|
||||
name: "scout",
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
grep: "allow",
|
||||
glob: "allow",
|
||||
webfetch: "allow",
|
||||
websearch: "allow",
|
||||
read: "allow",
|
||||
repo_clone: "allow",
|
||||
repo_overview: "allow",
|
||||
external_directory: {
|
||||
...readonlyExternalDirectory,
|
||||
[path.join(Global.Path.repos, "*")]: "allow",
|
||||
},
|
||||
}),
|
||||
user,
|
||||
),
|
||||
description: `Docs and dependency-source specialist. Use this when you need to inspect external documentation, clone dependency repositories into the managed cache, and research library implementation details without modifying the user's workspace.`,
|
||||
prompt: PROMPT_SCOUT,
|
||||
options: {},
|
||||
mode: "subagent" as const,
|
||||
native: true,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
// kilocode_change start - patch defaults with bash allowlist and recall permission
|
||||
const kilo = KiloAgent.prepare(cfg)
|
||||
const defaults = Permission.merge(baseDefaults, kilo.defaultsPatch)
|
||||
// kilocode_change end
|
||||
compaction: {
|
||||
name: "compaction",
|
||||
mode: "primary",
|
||||
native: true,
|
||||
hidden: true,
|
||||
prompt: PROMPT_COMPACTION,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
user,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
}),
|
||||
),
|
||||
options: {},
|
||||
},
|
||||
title: {
|
||||
name: "title",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
native: true,
|
||||
hidden: true,
|
||||
temperature: 0.5,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
user,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
}),
|
||||
),
|
||||
prompt: PROMPT_TITLE,
|
||||
},
|
||||
summary: {
|
||||
name: "summary",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
native: true,
|
||||
hidden: true,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
user,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
}),
|
||||
),
|
||||
prompt: PROMPT_SUMMARY,
|
||||
},
|
||||
}
|
||||
|
||||
// kilocode_change start - rename build→code, add debug/orchestrator/ask, patch plan/explore
|
||||
KiloAgent.patchAgents(agents, defaults, user, cfg, kilo, ctx.worktree, whitelistedDirs)
|
||||
const user = Permission.fromConfig(cfg.permission ?? {})
|
||||
|
||||
const agentConfigs = KiloAgent.preprocessConfig(cfg.agent ?? {})
|
||||
for (const [key, value] of Object.entries(agentConfigs)) {
|
||||
// kilocode_change end
|
||||
if (value.disable) {
|
||||
delete agents[key]
|
||||
continue
|
||||
}
|
||||
let item = agents[key]
|
||||
if (!item)
|
||||
item = agents[key] = {
|
||||
name: key,
|
||||
mode: "all",
|
||||
permission: Permission.merge(defaults, user),
|
||||
const agents: Record<string, Info> = {
|
||||
build: {
|
||||
name: "build",
|
||||
description: "The default agent. Executes tools based on configured permissions.",
|
||||
options: {},
|
||||
native: false,
|
||||
}
|
||||
if (value.model) item.model = Provider.parseModel(value.model)
|
||||
item.variant = value.variant ?? item.variant
|
||||
item.prompt = value.prompt ?? item.prompt
|
||||
item.description = value.description ?? item.description
|
||||
item.temperature = value.temperature ?? item.temperature
|
||||
item.topP = value.top_p ?? item.topP
|
||||
item.mode = value.mode ?? item.mode
|
||||
item.color = value.color ?? item.color
|
||||
item.hidden = value.hidden ?? item.hidden
|
||||
item.name = value.name ?? item.name
|
||||
item.steps = value.steps ?? item.steps
|
||||
// kilocode_change start - carry metadata as typed fields, never as provider options
|
||||
item.displayName = value.displayName ?? item.displayName
|
||||
item.source = value.source ?? item.source
|
||||
item.requirements = value.requirements ?? item.requirements
|
||||
// kilocode_change end
|
||||
item.options = mergeDeep(item.options, value.options ?? {})
|
||||
item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {}))
|
||||
KiloAgent.processConfigItem(item) // kilocode_change - populate displayName from options
|
||||
}
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
question: "allow",
|
||||
interactive_terminal: "allow", // kilocode_change
|
||||
suggest: "allow", // kilocode_change
|
||||
plan_enter: "allow",
|
||||
}),
|
||||
user,
|
||||
),
|
||||
mode: "primary",
|
||||
native: true,
|
||||
},
|
||||
plan: {
|
||||
name: "plan",
|
||||
description: "Plan mode. Disallows all edit tools.",
|
||||
options: {},
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
question: "allow",
|
||||
plan_exit: "allow",
|
||||
task: {
|
||||
general: "deny",
|
||||
},
|
||||
external_directory: {
|
||||
[path.join(Global.Path.data, "plans", "*")]: "allow",
|
||||
},
|
||||
edit: {
|
||||
"*": "deny",
|
||||
[path.join(".opencode", "plans", "*.md")]: "allow",
|
||||
[path.relative(ctx.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]: "allow",
|
||||
},
|
||||
}),
|
||||
user,
|
||||
),
|
||||
mode: "primary",
|
||||
native: true,
|
||||
},
|
||||
general: {
|
||||
name: "general",
|
||||
description: `General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel.`,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
todowrite: "deny",
|
||||
}),
|
||||
user,
|
||||
),
|
||||
options: {},
|
||||
mode: "subagent",
|
||||
native: true,
|
||||
},
|
||||
explore: {
|
||||
name: "explore",
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
grep: "allow",
|
||||
glob: "allow",
|
||||
list: "allow",
|
||||
bash: "allow",
|
||||
webfetch: "allow",
|
||||
websearch: "allow",
|
||||
read: "allow",
|
||||
external_directory: readonlyExternalDirectory,
|
||||
}),
|
||||
user,
|
||||
),
|
||||
description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
|
||||
prompt: PROMPT_EXPLORE,
|
||||
options: {},
|
||||
mode: "subagent",
|
||||
native: true,
|
||||
},
|
||||
// kilocode_change start - retain Kilo's opt-in repository research agent
|
||||
...(flags.experimentalScout
|
||||
? {
|
||||
scout: {
|
||||
name: "scout",
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
grep: "allow",
|
||||
glob: "allow",
|
||||
webfetch: "allow",
|
||||
websearch: "allow",
|
||||
read: "allow",
|
||||
repo_clone: "allow",
|
||||
repo_overview: "allow",
|
||||
external_directory: {
|
||||
...readonlyExternalDirectory,
|
||||
[path.join(Global.Path.repos, "*")]: "allow",
|
||||
},
|
||||
}),
|
||||
user,
|
||||
),
|
||||
description: `Docs and dependency-source specialist. Use this when you need to inspect external documentation, clone dependency repositories into the managed cache, and research library implementation details without modifying the user's workspace.`,
|
||||
prompt: PROMPT_SCOUT,
|
||||
options: {},
|
||||
mode: "subagent" as const,
|
||||
native: true,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
// kilocode_change end
|
||||
compaction: {
|
||||
name: "compaction",
|
||||
mode: "primary",
|
||||
native: true,
|
||||
hidden: true,
|
||||
prompt: PROMPT_COMPACTION,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
user,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
}),
|
||||
),
|
||||
options: {},
|
||||
},
|
||||
title: {
|
||||
name: "title",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
native: true,
|
||||
hidden: true,
|
||||
temperature: 0.5,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
user,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
}),
|
||||
),
|
||||
prompt: PROMPT_TITLE,
|
||||
},
|
||||
summary: {
|
||||
name: "summary",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
native: true,
|
||||
hidden: true,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
user,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
}),
|
||||
),
|
||||
prompt: PROMPT_SUMMARY,
|
||||
},
|
||||
}
|
||||
|
||||
// kilocode_change start - rename build→code, add debug/orchestrator/ask, patch plan/explore
|
||||
KiloAgent.patchAgents(agents, defaults, user, cfg, kilo, ctx.worktree, whitelistedDirs)
|
||||
|
||||
const agentConfigs = KiloAgent.preprocessConfig(cfg.agent ?? {})
|
||||
for (const [key, value] of Object.entries(agentConfigs)) {
|
||||
// kilocode_change end
|
||||
if (value.disable) {
|
||||
delete agents[key]
|
||||
continue
|
||||
}
|
||||
let item = agents[key]
|
||||
if (!item)
|
||||
item = agents[key] = {
|
||||
name: key,
|
||||
mode: "all",
|
||||
permission: Permission.merge(defaults, user),
|
||||
options: {},
|
||||
native: false,
|
||||
}
|
||||
if (value.model) item.model = Provider.parseModel(value.model)
|
||||
item.variant = value.variant ?? item.variant
|
||||
item.prompt = value.prompt ?? item.prompt
|
||||
item.description = value.description ?? item.description
|
||||
item.temperature = value.temperature ?? item.temperature
|
||||
item.topP = value.top_p ?? item.topP
|
||||
item.mode = value.mode ?? item.mode
|
||||
item.color = value.color ?? item.color
|
||||
item.hidden = value.hidden ?? item.hidden
|
||||
item.name = value.name ?? item.name
|
||||
item.steps = value.steps ?? item.steps
|
||||
// kilocode_change start - carry metadata as typed fields, never as provider options
|
||||
item.displayName = value.displayName ?? item.displayName
|
||||
item.source = value.source ?? item.source
|
||||
item.requirements = value.requirements ?? item.requirements
|
||||
// kilocode_change end
|
||||
item.options = mergeDeep(item.options, value.options ?? {})
|
||||
item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {}))
|
||||
KiloAgent.processConfigItem(item) // kilocode_change - populate displayName from options
|
||||
}
|
||||
|
||||
function referencePrompt(reference: KiloReference.Resolved) {
|
||||
if (reference.kind === "local") {
|
||||
return [
|
||||
`You are configured reference @${reference.name}, a read-only research agent for external reference material.`,
|
||||
`Local directory: ${reference.path}`,
|
||||
`Inspect this directory as the primary reference source. Prefer repo_overview with path ${JSON.stringify(reference.path)} before broader searches. Do not edit files.`,
|
||||
`Return exact absolute file paths for findings whenever possible.`,
|
||||
].join("\n\n")
|
||||
}
|
||||
|
||||
if (reference.kind === "invalid") {
|
||||
return [
|
||||
`You are configured reference @${reference.name}, but this reference is not usable yet.`,
|
||||
`Configured repository: ${reference.repository}`,
|
||||
`Problem: ${reference.message}`,
|
||||
`Explain this configuration problem if invoked. Do not edit files or attempt fallback clones.`,
|
||||
].join("\n\n")
|
||||
}
|
||||
|
||||
function referencePrompt(reference: ReferenceV1.Resolved) {
|
||||
if (reference.kind === "local") {
|
||||
return [
|
||||
`You are configured reference @${reference.name}, a read-only research agent for external reference material.`,
|
||||
`Local directory: ${reference.path}`,
|
||||
`Inspect this directory as the primary reference source. Prefer repo_overview with path ${JSON.stringify(reference.path)} before broader searches. Do not edit files.`,
|
||||
`Repository: ${reference.repository}`,
|
||||
...(reference.branch ? [`Branch/ref: ${reference.branch}`] : []),
|
||||
`Cached directory: ${reference.path}`,
|
||||
`Kilo materializes this configured repository before use. Do not call repo_clone for this reference.`, // kilocode_change
|
||||
`Inspect the cached directory as the primary reference source. Prefer repo_overview with path ${JSON.stringify(reference.path)} before broader searches, then use Glob, Grep, and Read inside that directory. Do not edit files.`,
|
||||
`Return exact absolute file paths for findings whenever possible.`,
|
||||
].join("\n\n")
|
||||
}
|
||||
|
||||
if (reference.kind === "invalid") {
|
||||
return [
|
||||
`You are configured reference @${reference.name}, but this reference is not usable yet.`,
|
||||
`Configured repository: ${reference.repository}`,
|
||||
`Problem: ${reference.message}`,
|
||||
`Explain this configuration problem if invoked. Do not edit files or attempt fallback clones.`,
|
||||
].join("\n\n")
|
||||
function referenceDescription(reference: KiloReference.Resolved) {
|
||||
if (reference.kind === "local") return `Scout reference for local directory ${reference.path}`
|
||||
if (reference.kind === "git") return `Scout reference for repository ${reference.repository}`
|
||||
return `Invalid Scout reference for repository ${reference.repository}`
|
||||
}
|
||||
|
||||
return [
|
||||
`You are configured reference @${reference.name}, a read-only research agent for external reference material.`,
|
||||
`Repository: ${reference.repository}`,
|
||||
...(reference.branch ? [`Branch/ref: ${reference.branch}`] : []),
|
||||
`Cached directory: ${reference.path}`,
|
||||
`Kilo materializes this configured repository before use. Do not call repo_clone for this reference.`, // kilocode_change
|
||||
`Inspect the cached directory as the primary reference source. Prefer repo_overview with path ${JSON.stringify(reference.path)} before broader searches, then use Glob, Grep, and Read inside that directory. Do not edit files.`,
|
||||
`Return exact absolute file paths for findings whenever possible.`,
|
||||
].join("\n\n")
|
||||
}
|
||||
|
||||
function referenceDescription(reference: ReferenceV1.Resolved) {
|
||||
if (reference.kind === "local") return `Scout reference for local directory ${reference.path}`
|
||||
if (reference.kind === "git") return `Scout reference for repository ${reference.repository}`
|
||||
return `Invalid Scout reference for repository ${reference.repository}`
|
||||
}
|
||||
|
||||
if (flags.experimentalScout) {
|
||||
const resolvedReferences = ReferenceV1.resolveAll({
|
||||
references: ConfigReference.normalize(cfg.reference ?? {}), // kilocode_change
|
||||
directory: ctx.directory,
|
||||
worktree: ctx.worktree,
|
||||
})
|
||||
for (const resolved of resolvedReferences) {
|
||||
if (agents[resolved.name]) continue
|
||||
const localPath = resolved.kind === "invalid" ? undefined : resolved.path
|
||||
agents[resolved.name] = {
|
||||
name: resolved.name,
|
||||
description: referenceDescription(resolved),
|
||||
permission: Permission.merge(
|
||||
agents.scout.permission,
|
||||
Permission.fromConfig({
|
||||
repo_clone: "deny",
|
||||
...(localPath
|
||||
? {
|
||||
external_directory: {
|
||||
[localPath]: "allow",
|
||||
[path.join(localPath, "*")]: "allow",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}),
|
||||
),
|
||||
prompt: referencePrompt(resolved),
|
||||
options: { reference: cfg.reference?.[resolved.name], resolved },
|
||||
mode: "subagent",
|
||||
native: false,
|
||||
if (flags.experimentalScout) {
|
||||
const resolvedReferences = KiloReference.resolveAll({
|
||||
references: cfg.reference ?? {}, // kilocode_change
|
||||
directory: ctx.directory,
|
||||
worktree: ctx.worktree,
|
||||
})
|
||||
for (const resolved of resolvedReferences) {
|
||||
if (agents[resolved.name]) continue
|
||||
const localPath = resolved.kind === "invalid" ? undefined : resolved.path
|
||||
agents[resolved.name] = {
|
||||
name: resolved.name,
|
||||
description: referenceDescription(resolved),
|
||||
permission: Permission.merge(
|
||||
agents.scout.permission,
|
||||
Permission.fromConfig({
|
||||
repo_clone: "deny",
|
||||
...(localPath
|
||||
? {
|
||||
external_directory: {
|
||||
[localPath]: "allow",
|
||||
[path.join(localPath, "*")]: "allow",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}),
|
||||
),
|
||||
prompt: referencePrompt(resolved),
|
||||
options: { reference: cfg.reference?.[resolved.name], resolved },
|
||||
mode: "subagent",
|
||||
native: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure Truncate.GLOB is allowed unless explicitly configured
|
||||
for (const name in agents) {
|
||||
const agent = agents[name]
|
||||
const explicit = agent.permission.some((r) => {
|
||||
if (r.permission !== "external_directory") return false
|
||||
if (r.action !== "deny") return false
|
||||
return r.pattern === Truncate.GLOB
|
||||
})
|
||||
if (explicit) continue
|
||||
// Ensure Truncate.GLOB is allowed unless explicitly configured
|
||||
for (const name in agents) {
|
||||
const agent = agents[name]
|
||||
const explicit = agent.permission.some((r) => {
|
||||
if (r.permission !== "external_directory") return false
|
||||
if (r.action !== "deny") return false
|
||||
return r.pattern === Truncate.GLOB
|
||||
})
|
||||
if (explicit) continue
|
||||
|
||||
agents[name].permission = Permission.merge(
|
||||
agents[name].permission,
|
||||
Permission.fromConfig({ external_directory: { [Truncate.GLOB]: "allow" } }),
|
||||
)
|
||||
}
|
||||
|
||||
KiloAgent.hardenSystemAgents(agents) // kilocode_change - keep system utility agents deny-only after config merges
|
||||
|
||||
const get = Effect.fnUntraced(function* (agent: string) {
|
||||
return agents[KiloAgent.resolveKey(agent)] // kilocode_change - treat "build" as "code"
|
||||
})
|
||||
|
||||
const list = Effect.fnUntraced(function* () {
|
||||
const cfg = yield* config.get()
|
||||
return pipe(
|
||||
agents,
|
||||
values(),
|
||||
sortBy(
|
||||
[(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "code"), "desc"], // kilocode_change - renamed from "build" to "code"
|
||||
[(x) => x.name, "asc"],
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
const defaultInfo = Effect.fnUntraced(function* () {
|
||||
const c = yield* config.get()
|
||||
if (c.default_agent) {
|
||||
// kilocode_change start
|
||||
const effective = KiloAgent.resolveKey(c.default_agent)
|
||||
const agent = agents[effective]
|
||||
// kilocode_change end
|
||||
if (!agent) throw new Error(`default agent "${c.default_agent}" not found`)
|
||||
if (agent.mode === "subagent") throw new Error(`default agent "${c.default_agent}" is a subagent`)
|
||||
if (agent.hidden === true) throw new Error(`default agent "${c.default_agent}" is hidden`)
|
||||
return agent
|
||||
agents[name].permission = Permission.merge(
|
||||
agents[name].permission,
|
||||
Permission.fromConfig({ external_directory: { [Truncate.GLOB]: "allow" } }),
|
||||
)
|
||||
}
|
||||
// kilocode_change start - prefer "code" as default agent (key order changes after rename from "build")
|
||||
const code = agents.code
|
||||
if (code && code.mode !== "subagent" && code.hidden !== true) return code
|
||||
// kilocode_change end
|
||||
const visible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true)
|
||||
if (!visible) throw new Error("no primary visible agent found")
|
||||
return visible
|
||||
})
|
||||
|
||||
const defaultAgent = Effect.fnUntraced(function* () {
|
||||
return (yield* defaultInfo()).name
|
||||
})
|
||||
KiloAgent.hardenSystemAgents(agents) // kilocode_change - keep system utility agents deny-only after config merges
|
||||
|
||||
return {
|
||||
version: KiloAgent.cacheKey(cfg), // kilocode_change
|
||||
get,
|
||||
list,
|
||||
defaultInfo,
|
||||
defaultAgent,
|
||||
} satisfies State
|
||||
}),
|
||||
)
|
||||
const get = Effect.fnUntraced(function* (agent: string) {
|
||||
return agents[KiloAgent.resolveKey(agent)] // kilocode_change - treat "build" as "code"
|
||||
})
|
||||
|
||||
const list = Effect.fnUntraced(function* () {
|
||||
const cfg = yield* config.get()
|
||||
return pipe(
|
||||
agents,
|
||||
values(),
|
||||
sortBy(
|
||||
[(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "code"), "desc"], // kilocode_change - renamed from "build" to "code"
|
||||
[(x) => x.name, "asc"],
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
const defaultInfo = Effect.fnUntraced(function* () {
|
||||
const c = yield* config.get()
|
||||
if (c.default_agent) {
|
||||
// kilocode_change start
|
||||
const effective = KiloAgent.resolveKey(c.default_agent)
|
||||
const agent = agents[effective]
|
||||
// kilocode_change end
|
||||
if (!agent) throw new Error(`default agent "${c.default_agent}" not found`)
|
||||
if (agent.mode === "subagent") throw new Error(`default agent "${c.default_agent}" is a subagent`)
|
||||
if (agent.hidden === true) throw new Error(`default agent "${c.default_agent}" is hidden`)
|
||||
return agent
|
||||
}
|
||||
// kilocode_change start - prefer "code" as default agent (key order changes after rename from "build")
|
||||
const code = agents.code
|
||||
if (code && code.mode !== "subagent" && code.hidden !== true) return code
|
||||
// kilocode_change end
|
||||
const visible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true)
|
||||
if (!visible) throw new Error("no primary visible agent found")
|
||||
return visible
|
||||
})
|
||||
|
||||
const defaultAgent = Effect.fnUntraced(function* () {
|
||||
return (yield* defaultInfo()).name
|
||||
})
|
||||
|
||||
return {
|
||||
version: KiloAgent.cacheKey(cfg), // kilocode_change
|
||||
get,
|
||||
list,
|
||||
defaultInfo,
|
||||
defaultAgent,
|
||||
} satisfies State
|
||||
}),
|
||||
)
|
||||
|
||||
// kilocode_change start - rebuild cached agents when permission-relevant config changes
|
||||
const current = Effect.fnUntraced(function* <A>(select: (s: State) => Effect.Effect<A>) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Identifier } from "@/id/id"
|
||||
import { context as instanceContext, type InstanceContext } from "@/project/instance-context" // kilocode_change
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { LocalContext } from "@/util/local-context" // kilocode_change
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node" // kilocode_change
|
||||
|
||||
const log = Log.create({ service: "bus" })
|
||||
|
||||
@@ -193,6 +194,7 @@ export const layer = Layer.effect(
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
export const node = LayerNode.make(layer, []) // kilocode_change
|
||||
|
||||
const { runPromise, runSync } = makeRuntime(Service, layer)
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ export const TuiThreadCommand = cmd({
|
||||
// kilocode_change end
|
||||
const auth = KiloTuiThreadDaemon.workerAuth() // kilocode_change - protect TUI-owned HTTP routes from unauthenticated local callers
|
||||
const worker = new Worker(file, {
|
||||
preload: ["@opentui/solid/preload"], // kilocode_change - Bun workers do not inherit the parent preload
|
||||
env: {
|
||||
...process.env,
|
||||
...auth.env, // kilocode_change
|
||||
|
||||
@@ -55,6 +55,9 @@ import {
|
||||
import { unique } from "remeda"
|
||||
// kilocode_change end
|
||||
import { withTransientReadRetry } from "@/util/effect-http-client"
|
||||
import * as Log from "@opencode-ai/core/util/log" // kilocode_change
|
||||
|
||||
const log = Log.create({ service: "config" }) // kilocode_change
|
||||
|
||||
// Custom merge function that concatenates array fields instead of replacing them
|
||||
// Keep remeda's deep conditional merge type out of hot config-loading paths; TS profiling showed it dominates here.
|
||||
@@ -70,7 +73,7 @@ function mergeConfigConcatArrays(target: Info, source: Info): Info {
|
||||
return merged
|
||||
}
|
||||
|
||||
function normalizeLoadedConfig(data: unknown) {
|
||||
function normalizeLoadedConfig(data: unknown, source: string) {
|
||||
if (!isRecord(data)) return data
|
||||
const copy = KilocodeConfig.retireIndexingFlag({ ...data }, source) // kilocode_change
|
||||
const hadLegacy = "theme" in copy || "keybinds" in copy || "tui" in copy
|
||||
@@ -78,6 +81,7 @@ function normalizeLoadedConfig(data: unknown) {
|
||||
delete copy.theme
|
||||
delete copy.keybinds
|
||||
delete copy.tui
|
||||
log.warn("tui keys in the main config are deprecated; move them to tui.json", { path: source }) // kilocode_change
|
||||
return copy
|
||||
}
|
||||
|
||||
@@ -307,7 +311,7 @@ export const layer = Layer.effect(
|
||||
),
|
||||
)
|
||||
const parsed = ConfigParse.jsonc(expanded, source)
|
||||
const data = ConfigParse.schema(ConfigV1.Info, normalizeLoadedConfig(parsed), source)
|
||||
const data = ConfigParse.schema(ConfigV1.Info, normalizeLoadedConfig(parsed, source), source)
|
||||
if (!("path" in options)) return data
|
||||
|
||||
yield* Effect.promise(() => resolveLoadedPlugins(data, options.path))
|
||||
@@ -1053,6 +1057,14 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node, Auth.node, Account.node, Env.node, Npm.node, httpClient])
|
||||
export const node = LayerNode.make(layer, [
|
||||
FSUtil.node,
|
||||
Auth.node,
|
||||
Account.node,
|
||||
Env.node,
|
||||
Npm.node,
|
||||
httpClient,
|
||||
Git.node,
|
||||
]) // kilocode_change
|
||||
|
||||
export * as Config from "./config"
|
||||
|
||||
@@ -39,6 +39,7 @@ export type HostMetadata = {
|
||||
|
||||
export interface Interface {
|
||||
readonly get: () => Effect.Effect<Resolved>
|
||||
readonly info: () => Effect.Effect<Info> // kilocode_change - editable config for Kilo console
|
||||
readonly pluginOrigins: () => Effect.Effect<ConfigPlugin.Origin[]>
|
||||
readonly waitForDependencies: () => Effect.Effect<void>
|
||||
}
|
||||
@@ -242,14 +243,15 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
|
||||
|
||||
// kilocode_change start - inject Kilo default plugins to keep TUI aligned with server config
|
||||
const defaults = KilocodeDefaultPlugins.apply(
|
||||
{ plugin: result.plugin, plugin_origins: acc.plugin_origins },
|
||||
{ plugin: result.plugin ? [...result.plugin] : undefined, plugin_origins: acc.plugin_origins },
|
||||
{ disabled: Flag.KILO_DISABLE_DEFAULT_PLUGINS },
|
||||
)
|
||||
result.plugin = defaults.plugin
|
||||
const config = { ...result, plugin: defaults.plugin }
|
||||
// kilocode_change end
|
||||
|
||||
return {
|
||||
config: result,
|
||||
config,
|
||||
info: { ...acc.result, plugin: defaults.plugin }, // kilocode_change - include applied Kilo defaults
|
||||
pluginOrigins: defaults.plugin_origins ?? [], // kilocode_change - exclude builtins from dependency installation
|
||||
dirs: result.plugin?.length ? dirs : [],
|
||||
}
|
||||
@@ -280,12 +282,13 @@ export const layer = Layer.effect(
|
||||
)
|
||||
|
||||
const get = Effect.fn("TuiConfig.get")(() => Effect.succeed(data.config))
|
||||
const info = Effect.fn("TuiConfig.info")(() => Effect.succeed(data.info)) // kilocode_change
|
||||
const pluginOrigins = Effect.fn("TuiConfig.pluginOrigins")(() => Effect.succeed(data.pluginOrigins))
|
||||
|
||||
const waitForDependencies = Effect.fn("TuiConfig.waitForDependencies")(() =>
|
||||
Effect.forEach(deps, Fiber.join, { concurrency: "unbounded" }).pipe(Effect.ignore(), Effect.asVoid),
|
||||
)
|
||||
return Service.of({ get, pluginOrigins, waitForDependencies })
|
||||
return Service.of({ get, info, pluginOrigins, waitForDependencies }) // kilocode_change
|
||||
}).pipe(Effect.withSpan("TuiConfig.layer")),
|
||||
)
|
||||
|
||||
@@ -301,6 +304,10 @@ export async function get() {
|
||||
return runPromise((svc) => svc.get())
|
||||
}
|
||||
|
||||
export async function info() {
|
||||
return runPromise((svc) => svc.info())
|
||||
}
|
||||
|
||||
export async function pluginOrigins() {
|
||||
return runPromise((svc) => svc.pluginOrigins())
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import { Permission } from "@/permission"
|
||||
import { withTimeout } from "@/util/timeout"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { cumulativeSessionDiff } from "@/kilocode/session-portability/cumulative-diff"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
async function provide<R>(input: { directory: string; fn: () => R }): Promise<R> {
|
||||
const { provide } = await import("@/kilocode/instance")
|
||||
@@ -382,6 +383,8 @@ export namespace KiloSessions {
|
||||
Layer.provide(Session.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [Bus.node, Config.node, Session.node])
|
||||
|
||||
export async function enableRemote() {
|
||||
if (remote) return
|
||||
if (ingestDisabled) return
|
||||
|
||||
@@ -18,6 +18,7 @@ import { MemoryService } from "@kilocode/kilo-memory/effect/service"
|
||||
import { MemoryEvents } from "@/kilocode/memory/events"
|
||||
import { installMemoryRuntime } from "@/kilocode/memory/runtime"
|
||||
import { KiloToolRegistry } from "@/kilocode/tool/registry"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
const log = Log.create({ service: "kilocode-bootstrap" })
|
||||
|
||||
@@ -99,4 +100,14 @@ export namespace KilocodeBootstrap {
|
||||
Bus.defaultLayer,
|
||||
]),
|
||||
)
|
||||
|
||||
const memory = LayerNode.make(MemoryService.layer, [])
|
||||
export const node = LayerNode.make(layer, [
|
||||
KiloSessions.node,
|
||||
Session.node,
|
||||
SessionSummary.node,
|
||||
Provider.node,
|
||||
memory,
|
||||
Bus.node,
|
||||
])
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import fuzzysort from "fuzzysort"
|
||||
import { Index, Show, createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { SplitBorder } from "@tui/ui/border"
|
||||
import { useBindings, useOpencodeModeStack } from "@tui/keymap"
|
||||
import { selectedForeground, useTheme } from "@tui/context/theme"
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { createEffect, createMemo, createSignal, For, Show } from "solid-js"
|
||||
import { type BoxRenderable, type MouseEvent, type TextareaRenderable } from "@opentui/core"
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { SplitBorder, EmptyBorder } from "@tui/component/border"
|
||||
import { SplitBorder, EmptyBorder } from "@tui/ui/border"
|
||||
import { useKV } from "@tui/context/kv"
|
||||
import { Spinner } from "@tui/component/spinner"
|
||||
import type { ChatMessage, TypingMember } from "./types"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { createEffect, on } from "solid-js"
|
||||
import { useKeyboard } from "@opentui/solid"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import * as Clipboard from "@tui/util/clipboard"
|
||||
import * as Clipboard from "@tui/clipboard"
|
||||
import { useBindings } from "@tui/keymap"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
@@ -34,6 +34,7 @@ export { KiloTerminalTitle } from "./terminal-title"
|
||||
// Hot reload TUI-local settings (keybinds/theme/ui) when changed from the Kilo Console.
|
||||
// Called from the App body (below SDKProvider and the TuiConfig provider).
|
||||
export { useTuiConfigHotReload } from "@/kilocode/cli/cmd/tui/context/tui-config-hot-reload"
|
||||
export { KiloTuiConfig } from "@/kilocode/cli/cmd/tui/context/tui-config"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
|
||||
@@ -6,14 +6,14 @@ import { MemoryDecisions } from "@kilocode/kilo-memory/decisions"
|
||||
import { MemoryToken } from "@kilocode/kilo-memory/token"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { createMemo, createResource, For, Match, Show, Switch } from "solid-js"
|
||||
import { relativeTime } from "@/cli/cmd/tui/feature-plugins/session/util"
|
||||
import { useProject } from "@/cli/cmd/tui/context/project"
|
||||
import { useSDK } from "@/cli/cmd/tui/context/sdk"
|
||||
import { useTheme } from "@/cli/cmd/tui/context/theme"
|
||||
import { useTuiConfig } from "@/cli/cmd/tui/context/tui-config"
|
||||
import { useBindings } from "@/cli/cmd/tui/keymap"
|
||||
import { useDialog, type DialogContext } from "@/cli/cmd/tui/ui/dialog"
|
||||
import { getScrollAcceleration } from "@/cli/cmd/tui/util/scroll"
|
||||
import { relativeTime } from "@/kilocode/cli/cmd/tui/relative-time"
|
||||
import { useProject } from "@tui/context/project"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { useTuiConfig } from "@tui/config"
|
||||
import { useBindings } from "@tui/keymap"
|
||||
import { useDialog, type DialogContext } from "@tui/ui/dialog"
|
||||
import { getScrollAcceleration } from "@tui/util/scroll"
|
||||
import { route } from "@/kilocode/cli/cmd/tui/memory-command"
|
||||
import { errorMessage } from "@/util/error"
|
||||
|
||||
@@ -163,9 +163,7 @@ export function DialogMemoryHelp(props: { reason?: string }) {
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<Show when={props.reason}>
|
||||
{(reason) => <text fg={theme.error}>{reason()}</text>}
|
||||
</Show>
|
||||
<Show when={props.reason}>{(reason) => <text fg={theme.error}>{reason()}</text>}</Show>
|
||||
<box gap={0}>
|
||||
<For each={MEMORY_COMMAND_CATALOG}>
|
||||
{(item) => (
|
||||
@@ -232,8 +230,8 @@ function DialogMemoryStatus(props: { workspace?: string; directory?: string }) {
|
||||
<box>
|
||||
<text fg={theme.text}>Startup context</text>
|
||||
<text fg={theme.textMuted}>
|
||||
{item().state.autoInject ? "on" : "off"} · last injected{" "}
|
||||
{fmt(item().state.stats.lastInjectedTokens)} tokens
|
||||
{item().state.autoInject ? "on" : "off"} · last injected {fmt(item().state.stats.lastInjectedTokens)}{" "}
|
||||
tokens
|
||||
</text>
|
||||
</box>
|
||||
<MemorySourcesInfo sources={item().sources} />
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useRoute } from "@tui/context/route"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { useTuiConfig } from "@tui/context/tui-config"
|
||||
import { useTuiConfig } from "@tui/config"
|
||||
import { useToast } from "@tui/ui/toast"
|
||||
import { getScrollAcceleration } from "@tui/util/scroll"
|
||||
import { errorMessage } from "@/util/error"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { KiloClient } from "@kilocode/sdk/v2"
|
||||
import type { CliRenderer } from "@opentui/core"
|
||||
import type { DialogContext } from "@/cli/cmd/tui/ui/dialog"
|
||||
import type { ToastContext } from "@/cli/cmd/tui/ui/toast"
|
||||
import type { DialogContext } from "@tui/ui/dialog"
|
||||
import type { ToastContext } from "@tui/ui/toast"
|
||||
import {
|
||||
showMemoryDialog,
|
||||
showMemoryHelpDialog,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TuiPluginApi } from "@kilocode/plugin/tui"
|
||||
import { MemoryAutosaveStatus } from "@kilocode/kilo-memory/autosave-status"
|
||||
import { createMemo, createResource, createSignal, onCleanup, onMount, Show } from "solid-js"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { relativeTime } from "@/cli/cmd/tui/feature-plugins/session/util"
|
||||
import { relativeTime } from "@/kilocode/cli/cmd/tui/relative-time"
|
||||
import { route } from "@/kilocode/cli/cmd/tui/memory-command"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { Locale } from "@/util/locale"
|
||||
@@ -39,12 +39,12 @@ export function MemorySidebar(props: { api: TuiPluginApi; sessionID: string }) {
|
||||
const [data] = createResource(
|
||||
() => `${workspace() ?? "__default__"}:${dir()}:${tick()}`,
|
||||
async () => {
|
||||
const status = await props.api.client.memory.status(route({ workspace: workspace(), directory: dir() })).catch(
|
||||
(error: unknown) => {
|
||||
const status = await props.api.client.memory
|
||||
.status(route({ workspace: workspace(), directory: dir() }))
|
||||
.catch((error: unknown) => {
|
||||
log.warn("memory status unavailable", { error: errorMessage(error) })
|
||||
return undefined
|
||||
},
|
||||
)
|
||||
})
|
||||
if (!status) return
|
||||
if (status.error || !status.data) return
|
||||
return status.data
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
* Kept separate from `tui-config.tsx` so the store factory has no SDK/event imports.
|
||||
*/
|
||||
import { onCleanup, onMount } from "solid-js"
|
||||
import type { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { useSDK } from "@/cli/cmd/tui/context/sdk"
|
||||
import { useEvent } from "@/cli/cmd/tui/context/event"
|
||||
import type { TuiConfig } from "@tui/config"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useEvent } from "@tui/context/event"
|
||||
import { KiloTuiConfig } from "./tui-config"
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,15 +7,13 @@
|
||||
*/
|
||||
import { createContext, useContext, type ParentProps } from "solid-js"
|
||||
import { createStore, reconcile } from "solid-js/store"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { TuiKeybind } from "@/cli/cmd/tui/config/keybind"
|
||||
import { KeymapLeaderTimeoutDefault } from "@/cli/cmd/tui/config/tui-schema"
|
||||
import { LeaderTimeoutDefault, TuiConfig, TuiConfigProvider, useTuiConfig } from "@tui/config"
|
||||
import { TuiKeybind } from "@tui/config/keybind"
|
||||
import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { KiloTitleIcon } from "@/kilocode/cli/cmd/tui/title-icon"
|
||||
|
||||
export type SetTuiConfig = (next: TuiConfig.Info) => void
|
||||
|
||||
const ConfigContext = createContext<TuiConfig.Resolved>()
|
||||
const SetContext = createContext<SetTuiConfig>()
|
||||
|
||||
export namespace KiloTuiConfig {
|
||||
@@ -39,7 +37,8 @@ export namespace KiloTuiConfig {
|
||||
commandMap: TuiKeybind.CommandMap,
|
||||
bindingDefaults: TuiKeybind.bindingDefaults(),
|
||||
}),
|
||||
leader_timeout: next.leader_timeout ?? KeymapLeaderTimeoutDefault,
|
||||
leader_timeout: next.leader_timeout ?? LeaderTimeoutDefault,
|
||||
mouse: next.mouse ?? true,
|
||||
}
|
||||
if (JSON.stringify(config.keybinds.bindings) === JSON.stringify(store.keybinds.bindings)) {
|
||||
config.keybinds = store.keybinds
|
||||
@@ -52,16 +51,14 @@ export namespace KiloTuiConfig {
|
||||
export function Provider(props: ParentProps<{ config: TuiConfig.Resolved }>) {
|
||||
const store = makeStore(props.config)
|
||||
return (
|
||||
<ConfigContext.Provider value={store.config}>
|
||||
<TuiConfigProvider config={store.config}>
|
||||
<SetContext.Provider value={store.set}>{props.children}</SetContext.Provider>
|
||||
</ConfigContext.Provider>
|
||||
</TuiConfigProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export function use() {
|
||||
const value = useContext(ConfigContext)
|
||||
if (!value) throw new Error("TuiConfig context must be used within a context provider")
|
||||
return value
|
||||
return useTuiConfig()
|
||||
}
|
||||
|
||||
export function useSet() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PermissionRequest } from "@kilocode/sdk/v2"
|
||||
import { useTheme } from "@/cli/cmd/tui/context/theme"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { MemoryPermissionRegistry } from "@/kilocode/cli/cmd/tui/routes/session/memory-permission"
|
||||
|
||||
function MemoryBody(props: { request: PermissionRequest }) {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export function relativeTime(timestamp: number) {
|
||||
const seconds = Math.floor((Date.now() - timestamp) / 1000)
|
||||
if (seconds < 60) return "just now"
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
if (minutes < 60) return `${minutes}m ago`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) return `${hours}h ago`
|
||||
const days = Math.floor(hours / 24)
|
||||
if (days < 7) return `${days}d ago`
|
||||
return new Date(timestamp).toLocaleDateString(undefined, { month: "short", day: "numeric" })
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
// When upstream adds a new command to index.ts, add it here too.
|
||||
import { AcpCommand } from "../cli/cmd/acp"
|
||||
import { McpCommand } from "../cli/cmd/mcp"
|
||||
import { TuiThreadCommand } from "../cli/cmd/tui/thread"
|
||||
import { AttachCommand } from "../cli/cmd/tui/attach"
|
||||
import { TuiThreadCommand } from "../cli/cmd/tui"
|
||||
import { AttachCommand } from "../cli/cmd/attach"
|
||||
import { RunCommand } from "../cli/cmd/run"
|
||||
import { GenerateCommand } from "../cli/cmd/generate"
|
||||
import { DebugCommand } from "../cli/cmd/debug"
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useDialog } from "@tui/ui/dialog"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { useToast } from "@tui/ui/toast"
|
||||
import { Link } from "@tui/ui/link"
|
||||
import * as Clipboard from "@tui/util/clipboard"
|
||||
import * as Clipboard from "@tui/clipboard"
|
||||
import { DialogKiloOrganization } from "./dialog-kilo-organization.js"
|
||||
|
||||
// These types are OpenCode-internal and imported at runtime
|
||||
@@ -43,7 +43,7 @@ export function KiloAutoMethod(props: KiloAutoMethodProps) {
|
||||
useKeyboard((evt: any) => {
|
||||
if (evt.name === "c" && !evt.ctrl && !evt.meta) {
|
||||
const code = props.authorization.instructions.match(/[A-Z0-9]{4}-[A-Z0-9]{4}/)?.[0] ?? props.authorization.url
|
||||
Clipboard.copy(code)
|
||||
Clipboard.write(code)
|
||||
.then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
|
||||
.catch(toast.error)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createMemo, Match, Switch, type JSX } from "solid-js"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { SplitBorder } from "@tui/ui/border"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { parseKiloErrorCode, kiloErrorTitle, kiloErrorDescription } from "@/kilocode/kilo-errors"
|
||||
import type { AssistantMessage } from "@kilocode/sdk/v2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Telemetry } from "@kilocode/kilo-telemetry"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { TuiEvent } from "@/cli/cmd/tui/event"
|
||||
import { TuiEvent } from "@/server/tui-event"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Identifier } from "@/id/id"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TuiPlugin } from "@kilocode/plugin/tui"
|
||||
import type { InternalTuiPlugin } from "@/cli/cmd/tui/plugin/internal"
|
||||
import type { InternalTuiPlugin } from "@/plugin/tui/internal"
|
||||
import { DialogMemoryHelp } from "@/kilocode/cli/cmd/tui/component/dialog-memory"
|
||||
|
||||
const id = "internal:kilo-memory-palette"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TuiPlugin } from "@kilocode/plugin/tui"
|
||||
import type { InternalTuiPlugin } from "@/cli/cmd/tui/plugin/internal"
|
||||
import type { InternalTuiPlugin } from "@/plugin/tui/internal"
|
||||
import { MemoryPermission } from "@/kilocode/cli/cmd/tui/permissions"
|
||||
|
||||
const id = "internal:kilo-permissions"
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { TuiPlugin, TuiPluginApi, TuiPluginModule } from "@kilocode/plugin/
|
||||
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
||||
import type { IndexingStatus, IndexingStatusState } from "@kilocode/kilo-indexing/status"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { useSync } from "@/cli/cmd/tui/context/sync"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { formatIndexingLabel } from "../indexing-label"
|
||||
import { indexingEnabled } from "../indexing-feature"
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TuiPlugin } from "@kilocode/plugin/tui"
|
||||
import type { InternalTuiPlugin } from "@/cli/cmd/tui/plugin/internal"
|
||||
import type { InternalTuiPlugin } from "@/plugin/tui/internal"
|
||||
import { MemorySidebar } from "@/kilocode/cli/cmd/tui/component/memory-sidebar"
|
||||
|
||||
const id = "internal:kilo-sidebar-memory"
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import path from "path"
|
||||
import { ConfigReference } from "@opencode-ai/core/config/reference"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { parseRepositoryReference, repositoryCachePath, type RemoteReference } from "@/util/repository"
|
||||
|
||||
export type Resolved =
|
||||
| {
|
||||
name: string
|
||||
kind: "local"
|
||||
path: string
|
||||
}
|
||||
| {
|
||||
name: string
|
||||
kind: "git"
|
||||
repository: string
|
||||
reference: RemoteReference
|
||||
path: string
|
||||
branch?: string
|
||||
}
|
||||
| {
|
||||
name: string
|
||||
kind: "invalid"
|
||||
repository?: string
|
||||
message: string
|
||||
}
|
||||
|
||||
type Normalized =
|
||||
| { kind: "local"; path: string }
|
||||
| { kind: "git"; repository: string; branch?: string }
|
||||
| { kind: "invalid"; message: string }
|
||||
|
||||
function normalize(name: string, entry: ConfigReference.Entry): Normalized {
|
||||
if (name.length === 0) return { kind: "invalid", message: "Reference alias must not be empty" }
|
||||
if (/[\/\s`,]/.test(name)) {
|
||||
return { kind: "invalid", message: "Reference alias must not contain /, whitespace, comma, or backtick" }
|
||||
}
|
||||
if (typeof entry === "string") {
|
||||
if (entry.startsWith(".") || entry.startsWith("/") || entry.startsWith("~")) {
|
||||
return { kind: "local", path: entry }
|
||||
}
|
||||
return { kind: "git", repository: entry }
|
||||
}
|
||||
if ("path" in entry) return { kind: "local", path: entry.path }
|
||||
return { kind: "git", repository: entry.repository, branch: entry.branch }
|
||||
}
|
||||
|
||||
function local(input: { directory: string; worktree: string; value: string }) {
|
||||
if (input.value.startsWith("~/")) return path.join(Global.Path.home, input.value.slice(2))
|
||||
if (path.isAbsolute(input.value)) return input.value
|
||||
return path.resolve(input.worktree === "/" ? input.directory : input.worktree, input.value)
|
||||
}
|
||||
|
||||
function resolve(name: string, entry: Normalized, directory: string, worktree: string): Resolved {
|
||||
if (entry.kind === "invalid") return { name, kind: "invalid", message: entry.message }
|
||||
if (entry.kind === "local") {
|
||||
return { name, kind: "local", path: local({ directory, worktree, value: entry.path }) }
|
||||
}
|
||||
const reference = parseRepositoryReference(entry.repository)
|
||||
if (!reference || reference.protocol === "file:") {
|
||||
return {
|
||||
name,
|
||||
kind: "invalid",
|
||||
repository: entry.repository,
|
||||
message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
|
||||
}
|
||||
}
|
||||
return {
|
||||
name,
|
||||
kind: "git",
|
||||
repository: entry.repository,
|
||||
reference,
|
||||
path: repositoryCachePath(reference),
|
||||
branch: entry.branch,
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveAll(input: { references: ConfigReference.Info; directory: string; worktree: string }) {
|
||||
const seen = new Map<string, { name: string; branch?: string }>()
|
||||
return Object.entries(input.references).map(([name, entry]) => {
|
||||
const item = resolve(name, normalize(name, entry), input.directory, input.worktree)
|
||||
if (item.kind !== "git") return item
|
||||
|
||||
const existing = seen.get(item.path)
|
||||
if (!existing) {
|
||||
seen.set(item.path, { name, branch: item.branch })
|
||||
return item
|
||||
}
|
||||
if (existing.branch === item.branch) return item
|
||||
|
||||
return {
|
||||
name,
|
||||
kind: "invalid" as const,
|
||||
repository: item.repository,
|
||||
message: `Reference conflicts with @${existing.name}: both use ${item.path}, but @${existing.name} requests ${existing.branch ?? "default branch"} and @${name} requests ${item.branch ?? "default branch"}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
import type { SuggestionRequest } from "@kilocode/sdk/v2"
|
||||
import { createMemo, createSignal, For } from "solid-js"
|
||||
import { useSDK } from "../../../cli/cmd/tui/context/sdk"
|
||||
import { selectedForeground, useTheme } from "../../../cli/cmd/tui/context/theme"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { selectedForeground, useTheme } from "@tui/context/theme"
|
||||
|
||||
export function SuggestBar(props: { request: SuggestionRequest }) {
|
||||
const sdk = useSDK()
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import type { SuggestionRequest } from "@kilocode/sdk/v2"
|
||||
import { createMemo, createSignal, For } from "solid-js"
|
||||
import { SplitBorder } from "../../../cli/cmd/tui/component/border"
|
||||
import { useSDK } from "../../../cli/cmd/tui/context/sdk"
|
||||
import { useTuiConfig } from "../../../cli/cmd/tui/context/tui-config"
|
||||
import { useBindings } from "../../../cli/cmd/tui/keymap"
|
||||
import { tint, useTheme } from "../../../cli/cmd/tui/context/theme"
|
||||
import { useDialog } from "../../../cli/cmd/tui/ui/dialog"
|
||||
import { SplitBorder } from "@tui/ui/border"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useTuiConfig } from "@tui/config"
|
||||
import { useBindings } from "@tui/keymap"
|
||||
import { tint, useTheme } from "@tui/context/theme"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
|
||||
// The footer-mounted overlay only ever hosts blocking suggestions now; the
|
||||
// built-in suggest tool emits non-blocking requests that render inline at
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
|
||||
import { createMemo, Match, Show, Switch, type JSX } from "solid-js"
|
||||
import { useTheme } from "../../../cli/cmd/tui/context/theme"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import type { SuggestionRequest, ToolPart as MessageToolPart } from "@kilocode/sdk/v2"
|
||||
import { SuggestBar } from "./bar"
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ import { applyEdits, modify } from "jsonc-parser"
|
||||
import { mergeDeep } from "remeda"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { ConfigParse } from "@/config/parse"
|
||||
import { CurrentWorkingDirectory } from "@/cli/cmd/tui/config/cwd"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { TuiInfo } from "@/cli/cmd/tui/config/tui-schema"
|
||||
import { CurrentWorkingDirectory } from "@/config/tui-cwd"
|
||||
import { TuiConfig } from "@/config/tui"
|
||||
import { KilocodeKeybinds } from "./keybinds"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { isRecord } from "@/util/record"
|
||||
@@ -18,7 +17,7 @@ export namespace KilocodeTuiConfig {
|
||||
export const Scope = z.enum(["project", "global"])
|
||||
export type Scope = z.infer<typeof Scope>
|
||||
|
||||
export const Patch = TuiInfo
|
||||
export const Patch = TuiConfig.Info
|
||||
export type Patch = Schema.Schema.Type<typeof Patch>
|
||||
export type Editable = Omit<Patch, "keybinds"> & { keybinds?: Record<string, string> }
|
||||
|
||||
@@ -90,7 +89,7 @@ export namespace KilocodeTuiConfig {
|
||||
function parse(input: string, file: string): Patch {
|
||||
const data = ConfigParse.jsonc(input, file)
|
||||
if (!isRecord(data)) return {}
|
||||
return writable(ConfigParse.schema(TuiInfo, normalize(data), file))
|
||||
return writable(ConfigParse.schema(TuiConfig.Info, normalize(data), file))
|
||||
}
|
||||
|
||||
function normalize(raw: Record<string, unknown>) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TuiKeybind } from "@/cli/cmd/tui/config/keybind"
|
||||
import { TuiKeybind } from "@opencode-ai/tui/config/keybind"
|
||||
import { Schema } from "effect"
|
||||
|
||||
export namespace KilocodeKeybinds {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ChildProcessWithoutNullStreams } from "child_process"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import * as Log from "@opencode-ai/core/util/log" // kilocode_change
|
||||
import { text } from "node:stream/consumers"
|
||||
import fs from "fs/promises"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
@@ -16,6 +17,7 @@ import { Npm } from "@opencode-ai/core/npm"
|
||||
import { TsCheck } from "../kilocode/ts-check" // kilocode_change
|
||||
import type { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
|
||||
const log = Log.create({ service: "lsp.server" }) // kilocode_change
|
||||
const pathExists = async (p: string) =>
|
||||
fs
|
||||
.stat(p)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { createConnection } from "net"
|
||||
import { createServer } from "http"
|
||||
import * as Log from "@opencode-ai/core/util/log" // kilocode_change
|
||||
import { OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_PATH, parseRedirectUri } from "./oauth-provider"
|
||||
import * as KiloOAuthCallback from "../kilocode/mcp-oauth-callback" // kilocode_change
|
||||
|
||||
const log = Log.create({ service: "mcp.oauth-callback" }) // kilocode_change
|
||||
|
||||
// Current callback server configuration (may differ from defaults if custom redirectUri is used)
|
||||
let currentPort = OAUTH_CALLBACK_PORT
|
||||
let currentPath = OAUTH_CALLBACK_PATH
|
||||
|
||||
@@ -497,6 +497,6 @@ export function toConfig(rules: Ruleset): ConfigPermissionV1.Info {
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
export const node = LayerNode.make(layer, [EventV2Bridge.node])
|
||||
export const node = LayerNode.make(layer, [EventV2Bridge.node, Config.node, Database.node]) // kilocode_change
|
||||
|
||||
export * as Permission from "."
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Hooks, PluginInput } from "@kilocode/plugin"
|
||||
import * as Log from "@opencode-ai/core/util/log" // kilocode_change
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { OAUTH_DUMMY_KEY } from "../../auth"
|
||||
import os from "os"
|
||||
@@ -7,6 +8,8 @@ import { createServer } from "http"
|
||||
import { refreshCodexAuth } from "@/kilocode/provider/codex-refresh" // kilocode_change
|
||||
import { OpenAIWebSocketPool } from "./ws-pool"
|
||||
|
||||
const log = Log.create({ service: "plugin.codex" }) // kilocode_change
|
||||
|
||||
const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann"
|
||||
const ISSUER = "https://auth.openai.com"
|
||||
const CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses"
|
||||
@@ -25,9 +28,7 @@ const ALLOWED_MODELS = new Set([
|
||||
// kilocode_change end
|
||||
])
|
||||
// kilocode_change start
|
||||
const DISALLOWED_MODELS = new Set([
|
||||
"gpt-5.5-pro",
|
||||
])
|
||||
const DISALLOWED_MODELS = new Set(["gpt-5.5-pro"])
|
||||
// kilocode_change end
|
||||
|
||||
interface PkceCodes {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import WebSocket from "ws"
|
||||
import * as Log from "@opencode-ai/core/util/log" // kilocode_change
|
||||
import { ProviderError } from "@/provider/error"
|
||||
import { isRecord } from "@/util/record"
|
||||
import { OpenAIWebSocket } from "./ws"
|
||||
|
||||
export const TITLE_HEADER = "x-kilo-title"
|
||||
|
||||
const log = Log.create({ service: "plugin.openai.ws" }) // kilocode_change
|
||||
|
||||
export interface CreateWebSocketFetchOptions {
|
||||
httpFetch?: typeof globalThis.fetch
|
||||
url?: string
|
||||
|
||||
@@ -78,6 +78,7 @@ export const node = LayerNode.make(layer, [
|
||||
LSP.node,
|
||||
Plugin.node,
|
||||
Project.node,
|
||||
KilocodeBootstrap.node, // kilocode_change
|
||||
Snapshot.node,
|
||||
Vcs.node,
|
||||
])
|
||||
|
||||
@@ -261,6 +261,6 @@ export const defaultLayer = Layer.suspend(() =>
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
export const node = LayerNode.make(layer, [Auth.node, Plugin.node])
|
||||
export const node = LayerNode.make(layer, [Auth.node, Plugin.node, ModelCache.node]) // kilocode_change
|
||||
|
||||
export * as ProviderAuth from "./auth"
|
||||
|
||||
@@ -6,6 +6,8 @@ import { Config } from "../config/config"
|
||||
import { Auth } from "../auth"
|
||||
import type { Provider } from "@opencode-ai/core/models-dev"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
|
||||
|
||||
type Models = Provider["models"]
|
||||
type KiloOptions = NonNullable<Parameters<typeof fetchKiloModels>[0]>
|
||||
@@ -282,4 +284,7 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(kiloModelsLayer),
|
||||
)
|
||||
|
||||
const kiloModels = LayerNode.make(kiloModelsLayer, [])
|
||||
export const node = LayerNode.make(layer, [Auth.node, Config.node, kiloModels, httpClient])
|
||||
|
||||
export * as ModelCache from "./model-cache"
|
||||
|
||||
@@ -6,6 +6,7 @@ import * as Core from "@opencode-ai/core/models-dev"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { AI_SDK_PROVIDERS, KILO_OPENROUTER_BASE, PROMPTS } from "@kilocode/kilo-gateway"
|
||||
import { overlay } from "@/kilocode/anaconda-desktop/provider"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
export const Model = Core.Model
|
||||
export type Model = Core.Model
|
||||
@@ -105,5 +106,7 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(ModelCache.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [Core.node, Config.node, Auth.node, ModelCache.node])
|
||||
|
||||
export { AI_SDK_PROVIDERS, PROMPTS }
|
||||
export * as ModelsDev from "./models"
|
||||
|
||||
@@ -728,6 +728,7 @@ export const node = LayerNode.make(layer, [
|
||||
Provider.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node, // kilocode_change
|
||||
])
|
||||
|
||||
export * as SessionCompaction from "./compaction"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { Provider } from "@/provider/provider"
|
||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { Log } from "@opencode-ai/core/util/log" // kilocode_change
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import * as Stream from "effect/Stream"
|
||||
import { streamText, wrapLanguageModel, type ModelMessage, type Tool } from "ai"
|
||||
@@ -39,6 +40,8 @@ import { LLMAISDK } from "./llm/ai-sdk"
|
||||
import { LLMNativeRuntime } from "./llm/native-runtime"
|
||||
import { LLMRequestPrep } from "./llm/request"
|
||||
|
||||
const log = Log.create({ service: "llm" }) // kilocode_change
|
||||
|
||||
export const OUTPUT_TOKEN_MAX = ProviderTransform.OUTPUT_TOKEN_MAX
|
||||
|
||||
export type StreamInput = {
|
||||
@@ -94,6 +97,7 @@ const live: Layer.Layer<
|
||||
const flags = yield* RuntimeFlags.Service
|
||||
|
||||
const run = Effect.fn("LLM.run")(function* (input: StreamRequest) {
|
||||
const l = log.clone().tag("providerID", input.model.providerID).tag("modelID", input.model.id) // kilocode_change
|
||||
yield* Effect.logInfo("stream", {
|
||||
providerID: input.model.providerID,
|
||||
modelID: input.model.id,
|
||||
|
||||
@@ -2052,7 +2052,10 @@ export const PromptInput = Schema.Struct({
|
||||
// `parts` type from the exported Schema input types so callers see a proper
|
||||
// tagged union.
|
||||
type PartInputUnion =
|
||||
MessageV2.TextPartInput | MessageV2.FilePartInput | MessageV2.AgentPartInput | MessageV2.SubtaskPartInput
|
||||
| MessageV2.TextPartInput
|
||||
| MessageV2.FilePartInput
|
||||
| MessageV2.AgentPartInput
|
||||
| MessageV2.SubtaskPartInput
|
||||
export type PromptInput = Omit<Schema.Schema.Type<typeof PromptInput>, "parts" | "editorContext"> & {
|
||||
parts: PartInputUnion[]
|
||||
editorContext?: MessageV2.EditorContext
|
||||
@@ -2169,6 +2172,7 @@ export const node = LayerNode.make(layer, [
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
Question.node, // kilocode_change
|
||||
])
|
||||
|
||||
export * as SessionPrompt from "./prompt"
|
||||
|
||||
@@ -195,6 +195,12 @@ export const DiffInput = Schema.Struct({
|
||||
})
|
||||
export type DiffInput = Schema.Schema.Type<typeof DiffInput>
|
||||
|
||||
export const node = LayerNode.make(layer, [Session.node, Snapshot.node, EventV2Bridge.node, Config.node])
|
||||
export const node = LayerNode.make(layer, [
|
||||
Session.node,
|
||||
Snapshot.node,
|
||||
EventV2Bridge.node,
|
||||
Config.node,
|
||||
Storage.node, // kilocode_change
|
||||
])
|
||||
|
||||
export * as SessionSummary from "./summary"
|
||||
|
||||
@@ -54,6 +54,6 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [Config.node, Session.node, ShareNext.node, RuntimeFlags.node])
|
||||
export const node = LayerNode.make(layer, [Config.node, Session.node, RuntimeFlags.node]) // kilocode_change
|
||||
|
||||
export * as SessionShare from "./session"
|
||||
|
||||
@@ -124,9 +124,7 @@ const add = Effect.fnUntraced(function* (state: State, match: Match, events: Eve
|
||||
}).pipe(
|
||||
Effect.catch(
|
||||
Effect.fnUntraced(function* (err) {
|
||||
const message = FrontmatterError.isInstance(err)
|
||||
? err.data.message
|
||||
: `Failed to parse skill ${match.path}` // kilocode_change
|
||||
const message = FrontmatterError.isInstance(err) ? err.data.message : `Failed to parse skill ${match.path}` // kilocode_change
|
||||
const { Session } = yield* Effect.promise(() => import("@/session/session"))
|
||||
yield* events.publish(Session.Event.Error, { error: new NamedError.Unknown({ message }).toObject() })
|
||||
yield* Effect.logError("failed to load skill", { skill: match.path, error: err }) // kilocode_change
|
||||
@@ -418,6 +416,7 @@ export const node = LayerNode.make(layer, [
|
||||
FSUtil.node,
|
||||
Global.node,
|
||||
RuntimeFlags.node,
|
||||
Git.node, // kilocode_change
|
||||
])
|
||||
|
||||
export * as Skill from "."
|
||||
|
||||
@@ -524,7 +524,7 @@ export const layer: Layer.Layer<Service, never, Requirements> =
|
||||
|
||||
const clash = (a: string, b: string) => a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`)
|
||||
|
||||
for (let i = 0; i < ops.length;) {
|
||||
for (let i = 0; i < ops.length; ) {
|
||||
const first = ops[i]!
|
||||
const run = [first]
|
||||
let j = i + 1
|
||||
@@ -960,6 +960,6 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(EffectFlock.defaultLayer), // kilocode_change
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node, AppProcess.node, Config.node])
|
||||
export const node = LayerNode.make(layer, [FSUtil.node, AppProcess.node, Config.node, EffectFlock.node]) // kilocode_change
|
||||
|
||||
export * as Snapshot from "."
|
||||
|
||||
@@ -64,6 +64,9 @@ import * as ToolNetwork from "@/kilocode/sandbox/network" // kilocode_change
|
||||
import { MemoryService } from "@kilocode/kilo-memory/effect/service" // kilocode_change
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache" // kilocode_change
|
||||
import { RipgrepBinary } from "@opencode-ai/core/ripgrep/binary" // kilocode_change
|
||||
import { AppProcess } from "@opencode-ai/core/process" // kilocode_change
|
||||
|
||||
export function webSearchEnabled(
|
||||
providerID: ProviderV2.ID,
|
||||
@@ -394,6 +397,8 @@ export const defaultLayer: Layer.Layer<Service> = Layer.suspend(
|
||||
// kilocode_change start
|
||||
Layer.provide(
|
||||
Ripgrep.layer.pipe(
|
||||
Layer.provide(RipgrepBinary.layer),
|
||||
Layer.provide(AppProcess.defaultLayer),
|
||||
Layer.provide(ToolNetwork.httpLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
@@ -408,6 +413,7 @@ export const defaultLayer: Layer.Layer<Service> = Layer.suspend(
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
Layer.provide(SessionStatus.defaultLayer),
|
||||
Layer.provide(RepositoryCache.defaultLayer),
|
||||
Layer.provide(Truncate.defaultLayer), // kilocode_change - split the pipe to stay within Effect's overload limit
|
||||
)
|
||||
.pipe(Layer.provide(Auth.defaultLayer)),
|
||||
@@ -494,6 +500,7 @@ function isJsonSchemaObject(value: unknown): value is Record<string, unknown> {
|
||||
const networkNode = LayerNode.make(ToolNetwork.httpLayer, [])
|
||||
const busNode = LayerNode.make(Bus.layer, [])
|
||||
const notebookNode = LayerNode.make(Notebook.defaultLayer, [])
|
||||
const repositoryCacheNode = LayerNode.make(RepositoryCache.defaultLayer, [])
|
||||
|
||||
export const node = LayerNode.make(layer.pipe(Layer.provide(Ripgrep.defaultLayer)), [
|
||||
Config.node,
|
||||
@@ -521,6 +528,7 @@ export const node = LayerNode.make(layer.pipe(Layer.provide(Ripgrep.defaultLayer
|
||||
Auth.node,
|
||||
SessionStatus.node,
|
||||
notebookNode,
|
||||
repositoryCacheNode,
|
||||
])
|
||||
// kilocode_change end
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Effect, Schema } from "effect"
|
||||
import DESCRIPTION from "./repo_clone.txt"
|
||||
import * as Tool from "./tool"
|
||||
import { repositoryCachePath } from "@/util/repository"
|
||||
import { RepositoryCache } from "@/reference/repository-cache"
|
||||
import { Global } from "@opencode-ai/core/global" // kilocode_change
|
||||
import { Repository } from "@opencode-ai/core/repository" // kilocode_change
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache" // kilocode_change
|
||||
|
||||
export const Parameters = Schema.Struct({
|
||||
repository: Schema.String.annotate({
|
||||
@@ -36,12 +37,12 @@ export const RepoCloneTool = Tool.define<typeof Parameters, Metadata, Repository
|
||||
parameters: Parameters,
|
||||
execute: (params: Schema.Schema.Type<typeof Parameters>, ctx: Tool.Context<Metadata>) =>
|
||||
Effect.gen(function* () {
|
||||
const reference = yield* RepositoryCache.parseRemoteReference(params.repository)
|
||||
const reference = yield* RepositoryCache.parseRemote(params.repository)
|
||||
if (params.branch) yield* RepositoryCache.validateBranch(params.branch)
|
||||
|
||||
const repository = reference.label
|
||||
const remote = reference.remote
|
||||
const localPath = repositoryCachePath(reference)
|
||||
const localPath = Repository.cachePath(Global.Path.repos, reference)
|
||||
|
||||
yield* ctx.ask({
|
||||
permission: "repo_clone",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { WarpGrepClient } from "@morphllm/morphsdk/tools/warp-grep/client" // ki
|
||||
import { Telemetry } from "@kilocode/kilo-telemetry" // kilocode_change
|
||||
import { Instance } from "../kilocode/instance" // kilocode_change
|
||||
import { EventV2Bridge } from "@/event-v2-bridge" // kilocode_change
|
||||
import { TuiEvent } from "../cli/cmd/tui/event" // kilocode_change
|
||||
import { TuiEvent } from "@/server/tui-event" // kilocode_change
|
||||
import DESCRIPTION from "./warpgrep.txt"
|
||||
|
||||
// FREE_PERIOD_TODO: Remove KILO_WARPGREP_PROXY_URL constant and the proxy
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// kilocode_change - new file
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { formatMarkdownTables } from "../../../src/cli/cmd/tui/util/markdown"
|
||||
import { formatMarkdownTables } from "@tui/util/markdown"
|
||||
|
||||
describe("formatMarkdownTables", () => {
|
||||
it("formats a simple table with fixed-width columns", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { $ } from "bun"
|
||||
import * as Observability from "@opencode-ai/core/effect/observability"
|
||||
import * as Observability from "@opencode-ai/core/observability"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import * as fs from "fs/promises"
|
||||
import os from "os"
|
||||
@@ -24,8 +24,7 @@ import { remove as cleanup } from "../kilocode/cleanup" // kilocode_change
|
||||
|
||||
const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
|
||||
export const testInstanceStoreLayer = InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap))
|
||||
const makeTestRuntime = () =>
|
||||
ManagedRuntime.make(testInstanceStoreLayer.pipe(Layer.provideMerge(Observability.layer)))
|
||||
const makeTestRuntime = () => ManagedRuntime.make(testInstanceStoreLayer.pipe(Layer.provideMerge(Observability.layer)))
|
||||
let testRuntime: ReturnType<typeof makeTestRuntime> | undefined
|
||||
const runtime = () => (testRuntime ??= makeTestRuntime())
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { createEffect, createRoot } from "solid-js"
|
||||
import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { TuiKeybind } from "@/cli/cmd/tui/config/keybind"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { TuiKeybind } from "@tui/config/keybind"
|
||||
import { TuiConfig } from "@tui/config"
|
||||
import { KiloTuiConfig } from "@/kilocode/cli/cmd/tui/context/tui-config"
|
||||
import { KiloTerminalTitle } from "@/kilocode/cli/cmd/tui/terminal-title"
|
||||
|
||||
@@ -33,6 +33,7 @@ function resolve(input: TuiConfig.Info): TuiConfig.Resolved {
|
||||
bindingDefaults: TuiKeybind.bindingDefaults(),
|
||||
}),
|
||||
leader_timeout: input.leader_timeout ?? 1_000,
|
||||
mouse: input.mouse ?? true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, mock, spyOn, 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"
|
||||
import { resolveThreadDirectory } from "../../../../src/cli/cmd/tui"
|
||||
import { KiloTuiThreadDaemon } from "../../../../src/kilocode/cli/cmd/tui/thread"
|
||||
import { DaemonClient } from "../../../../src/kilocode/daemon/client"
|
||||
|
||||
@@ -106,12 +106,12 @@ describe("kilo tui thread", () => {
|
||||
},
|
||||
}),
|
||||
}))
|
||||
mock.module("@/cli/cmd/tui/validate-session", () => ({
|
||||
mock.module("@/cli/tui/validate-session", () => ({
|
||||
validateSession: async (input: { sessionID?: string }) => {
|
||||
seen.push(input.sessionID ?? "")
|
||||
},
|
||||
}))
|
||||
mock.module("@/cli/cmd/tui/config/tui", () => ({
|
||||
mock.module("@/config/tui", () => ({
|
||||
TuiConfig: {
|
||||
get: async () => ({}),
|
||||
},
|
||||
|
||||
@@ -23,7 +23,6 @@ import { MessageV2 } from "../../src/session/message-v2"
|
||||
import * as SessionProcessorModule from "../../src/session/processor"
|
||||
import { Session as SessionNs } from "../../src/session/session"
|
||||
import { MessageID, PartID, SessionID } from "../../src/session/schema"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { SessionCompaction } from "../../src/session/compaction"
|
||||
import { SessionStatus } from "../../src/session/status"
|
||||
import { SessionSummary } from "../../src/session/summary"
|
||||
@@ -178,11 +177,9 @@ function runtime(layer: Layer.Layer<LLM.Service>, config = Config.defaultLayer)
|
||||
Layer.provide(config),
|
||||
Layer.provide(RuntimeFlags.layer()),
|
||||
Layer.provide(scope),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(SyncEvent.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,28 +7,14 @@ import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { Instruction } from "../../src/session/instruction"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { TestConfig } from "../fixture/config"
|
||||
import { provideInstance, testInstanceStoreLayer, tmpdirScoped } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const reference = Layer.mock(Reference.Service)({
|
||||
init: () => Effect.void,
|
||||
list: () => Effect.succeed([]),
|
||||
get: () => Effect.succeed(undefined),
|
||||
ensure: () => Effect.void,
|
||||
contains: () => Effect.succeed(false),
|
||||
})
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
NodeFileSystem.layer,
|
||||
reference,
|
||||
RuntimeFlags.layer(),
|
||||
testInstanceStoreLayer,
|
||||
),
|
||||
Layer.mergeAll(CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer, RuntimeFlags.layer(), testInstanceStoreLayer),
|
||||
)
|
||||
|
||||
const configLayer = TestConfig.layer()
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Global } from "@opencode-ai/core/global"
|
||||
import * as Log from "@opencode-ai/core/util/log"
|
||||
import { Agent } from "../../src/agent/agent"
|
||||
import { GlobalBus } from "../../src/bus/global"
|
||||
import { TuiEvent } from "../../src/cli/cmd/tui/event"
|
||||
import { TuiEvent } from "../../src/server/tui-event"
|
||||
import { Identifier } from "../../src/id/id"
|
||||
import { SessionID, MessageID, PartID } from "../../src/session/schema"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
@@ -68,16 +68,11 @@ const todo = {
|
||||
|
||||
const session = makeRuntime(Session.Service, Session.defaultLayer)
|
||||
const store = {
|
||||
create: (input?: Parameters<Session.Interface["create"]>[0]) =>
|
||||
session.runPromise((svc) => svc.create(input)),
|
||||
get: (id: SessionID) =>
|
||||
session.runPromise((svc) => svc.get(id)),
|
||||
messages: (input: Parameters<Session.Interface["messages"]>[0]) =>
|
||||
session.runPromise((svc) => svc.messages(input)),
|
||||
updateMessage: <T extends MessageV2.Info>(msg: T) =>
|
||||
session.runPromise((svc) => svc.updateMessage(msg)),
|
||||
updatePart: <T extends MessageV2.Part>(part: T) =>
|
||||
session.runPromise((svc) => svc.updatePart(part)),
|
||||
create: (input?: Parameters<Session.Interface["create"]>[0]) => session.runPromise((svc) => svc.create(input)),
|
||||
get: (id: SessionID) => session.runPromise((svc) => svc.get(id)),
|
||||
messages: (input: Parameters<Session.Interface["messages"]>[0]) => session.runPromise((svc) => svc.messages(input)),
|
||||
updateMessage: <T extends MessageV2.Info>(msg: T) => session.runPromise((svc) => svc.updateMessage(msg)),
|
||||
updatePart: <T extends MessageV2.Part>(part: T) => session.runPromise((svc) => svc.updatePart(part)),
|
||||
}
|
||||
|
||||
const model = {
|
||||
@@ -901,7 +896,9 @@ describe("plan follow-up", () => {
|
||||
|
||||
test("ask - falls back to configured code model when saved CLI code model is unavailable", () =>
|
||||
withInstance(async () => {
|
||||
await writeState({ model: { code: { providerID: ProviderV2.ID.make("missing"), modelID: ModelV2.ID.make("ghost") } } })
|
||||
await writeState({
|
||||
model: { code: { providerID: ProviderV2.ID.make("missing"), modelID: ModelV2.ID.make("ghost") } },
|
||||
})
|
||||
const get = spyOn(PlanFollowupRuntime, "agent").mockImplementation(async (name: string) => {
|
||||
if (name === "code") {
|
||||
return {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "../../src/event-v2-bridge"
|
||||
import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Env } from "../../src/env"
|
||||
import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Format } from "../../src/format"
|
||||
import { Git } from "../../src/git"
|
||||
@@ -33,8 +33,7 @@ import { Provider as ProviderSvc } from "../../src/provider/provider"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Question } from "../../src/question"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { RepositoryCache } from "../../src/reference/repository-cache"
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache"
|
||||
import { Session } from "../../src/session/session"
|
||||
import { SessionCompaction } from "../../src/session/compaction"
|
||||
import { Instruction } from "../../src/session/instruction"
|
||||
@@ -154,7 +153,6 @@ function makeHttp() {
|
||||
SyncEvent.defaultLayer,
|
||||
EventV2Bridge.defaultLayer,
|
||||
Database.defaultLayer,
|
||||
Reference.defaultLayer,
|
||||
status,
|
||||
MemoryService.layer,
|
||||
).pipe(Layer.provideMerge(infra))
|
||||
@@ -208,7 +206,6 @@ function makeHttp() {
|
||||
Bus.layer,
|
||||
infra,
|
||||
Storage.defaultLayer,
|
||||
Reference.defaultLayer,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -22,7 +22,6 @@ import { KiloCompactionChunks } from "../../src/kilocode/session/compaction-chun
|
||||
import { KiloSessionCompaction } from "../../src/kilocode/session/compaction"
|
||||
import { LLM } from "../../src/session/llm"
|
||||
import { MessageV2 } from "../../src/session/message-v2"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { SessionCompaction } from "../../src/session/compaction"
|
||||
import * as SessionProcessorModule from "../../src/session/processor"
|
||||
import type { SessionProcessor } from "../../src/session/processor"
|
||||
@@ -40,6 +39,9 @@ import { remove as cleanup } from "./cleanup"
|
||||
const providerID = ProviderV2.ID.make("test")
|
||||
const modelID = ModelV2.ID.make("test-model")
|
||||
const ref = { providerID, modelID }
|
||||
const agents = Layer.mock(Agent.Service)({
|
||||
get: () => Effect.succeed({ name: "compaction", mode: "primary", permission: [], options: {} } satisfies Agent.Info),
|
||||
})
|
||||
const previous = Flag.KILO_DB
|
||||
const dbfile = path.join(os.tmpdir(), `kilo-compaction-chunks-${process.pid}-${crypto.randomUUID()}.db`)
|
||||
|
||||
@@ -227,13 +229,12 @@ function fakeRuntime(outputTokenMax?: number, error?: MessageV2.Assistant["error
|
||||
Layer.mergeAll(SessionCompaction.layer.pipe(Layer.provide(processor)), processor, bus).pipe(
|
||||
Layer.provide(ProviderTest.fake({ model }).layer),
|
||||
Layer.provide(SessionNs.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(agents),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(SyncEvent.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ outputTokenMax })),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(bus),
|
||||
Layer.provide(
|
||||
Layer.mock(Config.Service)({
|
||||
@@ -309,7 +310,6 @@ function liveRuntime(layer: Layer.Layer<LLM.Service>, context = 10_000) {
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer()),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(status),
|
||||
Layer.provide(bus),
|
||||
Layer.provide(
|
||||
|
||||
@@ -16,7 +16,6 @@ import { Plugin } from "../../src/plugin"
|
||||
import type { Provider } from "../../src/provider/provider"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { Session } from "../../src/session/session"
|
||||
import { LLM } from "../../src/session/llm"
|
||||
import { MessageV2 } from "../../src/session/message-v2"
|
||||
@@ -100,13 +99,6 @@ const llm = Layer.unwrap(
|
||||
}),
|
||||
)
|
||||
|
||||
const reference = Layer.mock(Reference.Service)({
|
||||
init: () => Effect.void,
|
||||
list: () => Effect.succeed([]),
|
||||
get: () => Effect.succeed(undefined),
|
||||
ensure: () => Effect.void,
|
||||
contains: () => Effect.succeed(false),
|
||||
})
|
||||
const status = Layer.mergeAll(SessionStatus.defaultLayer, Bus.layer)
|
||||
const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer)
|
||||
const deps = Layer.mergeAll(
|
||||
@@ -117,7 +109,6 @@ const deps = Layer.mergeAll(
|
||||
Plugin.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
RuntimeFlags.layer(),
|
||||
reference,
|
||||
SessionSummary.defaultLayer,
|
||||
Image.defaultLayer,
|
||||
SyncEvent.defaultLayer,
|
||||
@@ -126,7 +117,7 @@ const deps = Layer.mergeAll(
|
||||
status,
|
||||
llm,
|
||||
).pipe(Layer.provideMerge(infra))
|
||||
const env = SessionProcessor.layer.pipe(Layer.provideMerge(deps), Layer.provide(reference))
|
||||
const env = SessionProcessor.layer.pipe(Layer.provideMerge(deps))
|
||||
|
||||
const it = testEffect(env)
|
||||
|
||||
@@ -489,9 +480,7 @@ describe("session processor empty tool-calls", () => {
|
||||
LLMEvent.stepFinish({ index: 0, reason: "stop", usage: usage() }),
|
||||
LLMEvent.finish({ reason: "stop", usage: usage() }),
|
||||
).pipe(
|
||||
Stream.tap((event) =>
|
||||
event.type === "step-finish" ? state.session.remove(state.chat.id) : Effect.void,
|
||||
),
|
||||
Stream.tap((event) => (event.type === "step-finish" ? state.session.remove(state.chat.id) : Effect.void)),
|
||||
),
|
||||
)
|
||||
const result = yield* state.handle.process(state.input)
|
||||
|
||||
@@ -16,7 +16,6 @@ import { Plugin } from "../../src/plugin"
|
||||
import type { Provider } from "../../src/provider/provider"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { Session } from "../../src/session/session"
|
||||
import { LLM } from "../../src/session/llm"
|
||||
import { MessageV2 } from "../../src/session/message-v2"
|
||||
@@ -98,13 +97,6 @@ const llm = Layer.unwrap(
|
||||
}),
|
||||
)
|
||||
|
||||
const reference = Layer.mock(Reference.Service)({
|
||||
init: () => Effect.void,
|
||||
list: () => Effect.succeed([]),
|
||||
get: () => Effect.succeed(undefined),
|
||||
ensure: () => Effect.void,
|
||||
contains: () => Effect.succeed(false),
|
||||
})
|
||||
const status = Layer.mergeAll(SessionStatus.defaultLayer, Bus.layer)
|
||||
const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer)
|
||||
const deps = Layer.mergeAll(
|
||||
@@ -115,7 +107,6 @@ const deps = Layer.mergeAll(
|
||||
Plugin.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
RuntimeFlags.layer(),
|
||||
reference,
|
||||
SessionSummary.defaultLayer,
|
||||
Image.defaultLayer,
|
||||
SyncEvent.defaultLayer,
|
||||
@@ -124,7 +115,7 @@ const deps = Layer.mergeAll(
|
||||
status,
|
||||
llm,
|
||||
).pipe(Layer.provideMerge(infra))
|
||||
const env = SessionProcessor.layer.pipe(Layer.provideMerge(deps), Layer.provide(reference))
|
||||
const env = SessionProcessor.layer.pipe(Layer.provideMerge(deps))
|
||||
|
||||
const it = testEffect(env)
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import { Plugin } from "../../src/plugin"
|
||||
import type { Provider } from "../../src/provider/provider"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { Session } from "../../src/session/session"
|
||||
import { LLM } from "../../src/session/llm"
|
||||
import { MessageV2 } from "../../src/session/message-v2"
|
||||
@@ -111,13 +110,6 @@ const llm = Layer.unwrap(
|
||||
}),
|
||||
)
|
||||
|
||||
const reference = Layer.mock(Reference.Service)({
|
||||
init: () => Effect.void,
|
||||
list: () => Effect.succeed([]),
|
||||
get: () => Effect.succeed(undefined),
|
||||
ensure: () => Effect.void,
|
||||
contains: () => Effect.succeed(false),
|
||||
})
|
||||
const status = Layer.mergeAll(SessionStatus.defaultLayer, Bus.layer)
|
||||
const infra = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer)
|
||||
const deps = Layer.mergeAll(
|
||||
@@ -128,7 +120,6 @@ const deps = Layer.mergeAll(
|
||||
Plugin.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
RuntimeFlags.layer(),
|
||||
reference,
|
||||
SessionSummary.defaultLayer,
|
||||
Image.defaultLayer,
|
||||
SyncEvent.defaultLayer,
|
||||
@@ -137,7 +128,7 @@ const deps = Layer.mergeAll(
|
||||
status,
|
||||
llm,
|
||||
).pipe(Layer.provideMerge(infra))
|
||||
const env = SessionProcessor.layer.pipe(Layer.provideMerge(deps), Layer.provide(reference))
|
||||
const env = SessionProcessor.layer.pipe(Layer.provideMerge(deps))
|
||||
|
||||
const it = testEffect(env)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "../../src/event-v2-bridge"
|
||||
import * as CrossSpawnSpawner from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Env } from "../../src/env"
|
||||
import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Format } from "../../src/format"
|
||||
import { Git } from "../../src/git"
|
||||
@@ -30,8 +30,7 @@ import { Provider as ProviderSvc } from "../../src/provider/provider"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Question } from "../../src/question"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { RepositoryCache } from "../../src/reference/repository-cache"
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache"
|
||||
import { Session } from "../../src/session/session"
|
||||
import { SessionCompaction } from "../../src/session/compaction"
|
||||
import { Instruction } from "../../src/session/instruction"
|
||||
@@ -144,7 +143,6 @@ function makeHttp() {
|
||||
lsp,
|
||||
mcp,
|
||||
FSUtil.defaultLayer,
|
||||
Reference.defaultLayer,
|
||||
SyncEvent.defaultLayer,
|
||||
EventV2Bridge.defaultLayer,
|
||||
Database.defaultLayer,
|
||||
@@ -161,7 +159,6 @@ function makeHttp() {
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(Command.defaultLayer),
|
||||
Layer.provide(Auth.defaultLayer), // kilocode_change
|
||||
Layer.provideMerge(todo),
|
||||
@@ -202,7 +199,6 @@ function makeHttp() {
|
||||
Bus.layer,
|
||||
infra,
|
||||
Storage.defaultLayer,
|
||||
Reference.defaultLayer,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -25,8 +25,7 @@ import { Permission } from "../../src/permission"
|
||||
import { Plugin } from "../../src/plugin"
|
||||
import { Provider as ProviderSvc } from "../../src/provider/provider"
|
||||
import { Question } from "../../src/question"
|
||||
import { Reference } from "../../src/reference/reference"
|
||||
import { RepositoryCache } from "../../src/reference/repository-cache"
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache"
|
||||
import { SessionCompaction } from "../../src/session/compaction"
|
||||
import { Instruction } from "../../src/session/instruction"
|
||||
import { LLM } from "../../src/session/llm"
|
||||
@@ -43,7 +42,7 @@ import { Skill } from "../../src/skill"
|
||||
import { Snapshot } from "../../src/snapshot"
|
||||
import { Storage } from "../../src/storage/storage"
|
||||
import { SyncEvent } from "../../src/sync"
|
||||
import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { ToolRegistry } from "../../src/tool/registry"
|
||||
import { Truncate } from "../../src/tool/truncate"
|
||||
import { KiloHeadless } from "../../src/kilocode/permission/headless"
|
||||
@@ -139,7 +138,6 @@ function makeHttp() {
|
||||
lsp,
|
||||
mcp,
|
||||
FSUtil.defaultLayer,
|
||||
Reference.defaultLayer,
|
||||
SyncEvent.defaultLayer,
|
||||
EventV2Bridge.defaultLayer,
|
||||
Database.defaultLayer,
|
||||
@@ -156,7 +154,6 @@ function makeHttp() {
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(Reference.defaultLayer),
|
||||
Layer.provide(Command.defaultLayer),
|
||||
Layer.provide(Auth.defaultLayer), // kilocode_change
|
||||
Layer.provideMerge(todo),
|
||||
@@ -197,7 +194,6 @@ function makeHttp() {
|
||||
Bus.layer,
|
||||
infra,
|
||||
Storage.defaultLayer,
|
||||
Reference.defaultLayer,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -341,8 +337,8 @@ it.live("headless run: subagent permission asks fail instead of waiting forever"
|
||||
|
||||
expect(err).toBeInstanceOf(Permission.DeniedError)
|
||||
expect(yield* permission.list()).toEqual([])
|
||||
expect(yield* KiloHeadless.denies(child.id)).toBe(true)
|
||||
expect(yield* KiloHeadless.denies(root.id)).toBe(false)
|
||||
expect(yield* KiloHeadless.denies(child.id)).toBe(true)
|
||||
expect(yield* KiloHeadless.denies(root.id)).toBe(false)
|
||||
|
||||
KiloHeadless.clear(root.id)
|
||||
}),
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Global } from "@opencode-ai/core/global"
|
||||
import { MessageID, SessionID } from "../../../src/session/schema"
|
||||
import { Truncate } from "../../../src/tool/truncate"
|
||||
import { RepoCloneTool } from "../../../src/tool/repo_clone"
|
||||
import { RepositoryCache } from "../../../src/reference/repository-cache"
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache"
|
||||
import { disposeAllInstances, provideTmpdirInstance, tmpdirScoped } from "../../fixture/fixture"
|
||||
import { testEffect } from "../../lib/effect"
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { BackgroundProcessInfo, GlobalEvent } from "@kilocode/sdk/v2"
|
||||
import { normalizeSyncEvent } from "../../src/cli/cmd/tui/context/event"
|
||||
import { mount, wait } from "../cli/cmd/tui/sync-fixture"
|
||||
import { normalizeSyncEvent } from "@tui/context/event"
|
||||
import { mount, wait } from "../../../tui/test/cli/cmd/tui/sync-fixture"
|
||||
|
||||
function processInfo(id: string, sessionID: string, lifetime: BackgroundProcessInfo["lifetime"], updated: number) {
|
||||
return {
|
||||
@@ -80,7 +80,7 @@ describe("TUI sync event wire format", () => {
|
||||
try {
|
||||
emit({
|
||||
directory: "/tmp/opencode/packages/opencode",
|
||||
project: "proj_test",
|
||||
project: "proj_other",
|
||||
payload: {
|
||||
type: "background_process.updated",
|
||||
properties: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { createRoot } from "solid-js"
|
||||
import { createLeadingTrailingSignal } from "@tui/feature-plugins/session/preview-pane"
|
||||
import { createDebouncedSignal } from "@tui/util/signal"
|
||||
|
||||
describe("TUI scheduling", () => {
|
||||
@@ -17,24 +16,4 @@ describe("TUI scheduling", () => {
|
||||
dispose()
|
||||
})
|
||||
})
|
||||
|
||||
test("updates on the leading and trailing edges", async () => {
|
||||
await createRoot(async (dispose) => {
|
||||
const [value, , schedule] = createLeadingTrailingSignal("initial", 10)
|
||||
|
||||
schedule("leading")
|
||||
expect(value()).toBe("leading")
|
||||
|
||||
schedule("middle")
|
||||
schedule("trailing")
|
||||
expect(value()).toBe("leading")
|
||||
|
||||
await Bun.sleep(30)
|
||||
expect(value()).toBe("trailing")
|
||||
|
||||
schedule("next")
|
||||
expect(value()).toBe("next")
|
||||
dispose()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as TestClock from "effect/testing/TestClock"
|
||||
import * as TestConsole from "effect/testing/TestConsole"
|
||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||
import type { Config } from "@/config/config"
|
||||
import { Reference } from "@/reference/reference" // kilocode_change
|
||||
import { TestInstance, withTmpdirInstance } from "../fixture/fixture"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
|
||||
@@ -138,10 +137,8 @@ const liveEnv = TestConsole.layer
|
||||
export const it = make<never, never>(testEnv, liveEnv)
|
||||
|
||||
// kilocode_change start
|
||||
export const testEffect = <R, E>(layer: Layer.Layer<R, E>) => {
|
||||
const full = Layer.merge(layer, Reference.defaultLayer)
|
||||
return make(Layer.provideMerge(full, testEnv), Layer.provideMerge(full, liveEnv))
|
||||
}
|
||||
export const testEffect = <R, E>(layer: Layer.Layer<R, E>) =>
|
||||
make<R, E>(Layer.provideMerge(layer, testEnv), Layer.provideMerge(layer, liveEnv))
|
||||
export const testEffectBare = <R, E>(layer: Layer.Layer<R, E>) =>
|
||||
make<R, E>(Layer.provideMerge(layer, testEnv), Layer.provideMerge(layer, liveEnv))
|
||||
// kilocode_change end
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import os from "os"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
import { afterAll } from "bun:test"
|
||||
import { remove as cleanup } from "./kilocode/cleanup" // kilocode_change
|
||||
|
||||
|
||||
@@ -259,7 +259,7 @@ describe("event HttpApi", () => {
|
||||
timestamp,
|
||||
messageID: SessionMessageID.ID.create(),
|
||||
delivery: "queue",
|
||||
prompt: new Prompt({ text: "hello", files: [], agents: [], references: [] }), // kilocode_change - upstream made prompt a Prompt class
|
||||
prompt: new Prompt({ text: "hello", files: [], agents: [] }), // kilocode_change - upstream made prompt a Prompt class
|
||||
})
|
||||
expect(properties(yield* Fiber.join(prompted))).toMatchObject({
|
||||
timestamp: 1_234,
|
||||
|
||||
@@ -187,15 +187,15 @@ const env = LayerNode.buildLayer(LayerNode.group([root, LayerNode.make(TestLLMSe
|
||||
const it = testEffect(env)
|
||||
// kilocode_change start - exercise non-default output token ceilings in the processor
|
||||
const capped = testEffect(
|
||||
Layer.mergeAll(
|
||||
TestLLMServer.layer,
|
||||
SessionProcessor.layer.pipe(
|
||||
Layer.provide(summary),
|
||||
Layer.provide(Image.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true, outputTokenMax: 8_000 })),
|
||||
Layer.provideMerge(deps),
|
||||
),
|
||||
),
|
||||
LayerNode.buildLayer(LayerNode.group([root, LayerNode.make(TestLLMServer.layer, [])]), {
|
||||
replacements: [
|
||||
LayerNode.replace(SessionSummary.node, summary),
|
||||
LayerNode.replace(
|
||||
RuntimeFlags.node,
|
||||
RuntimeFlags.layer({ experimentalEventSystem: true, outputTokenMax: 8_000 }),
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ import { awaitWithTimeout, pollWithTimeout, testEffect } from "../lib/effect"
|
||||
import { reply, TestLLMServer } from "../lib/llm-server"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { MemoryService } from "@kilocode/kilo-memory/effect/service" // kilocode_change
|
||||
import { RepositoryCache } from "@opencode-ai/core/repository-cache" // kilocode_change
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
|
||||
@@ -225,6 +226,7 @@ function makePrompt(input?: { processor?: "blocking" }) {
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(RepositoryCache.defaultLayer), // kilocode_change - RepoCloneTool dependency
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
Layer.provide(Auth.defaultLayer), // kilocode_change
|
||||
@@ -705,7 +707,7 @@ it.instance("loop surfaces content-filter finishes as session errors", () =>
|
||||
const off = yield* events.listen((event) => {
|
||||
if (event.type !== Session.Event.Error.type) return Effect.void
|
||||
const data = event.data as typeof Session.Event.Error.data.Type
|
||||
if (data.sessionID === chat.id && data.error) errors.push(data.error)
|
||||
if (data.sessionID === chat.id && data.error?.name === "ContentFilterError") errors.push(data.error)
|
||||
return Effect.void
|
||||
})
|
||||
|
||||
@@ -1046,7 +1048,7 @@ it.instance("subtask child inherits parent session external_directory allow", ()
|
||||
}),
|
||||
)
|
||||
|
||||
noLLMServer.instance("prompt tools replace previous prompt tool rules", () =>
|
||||
noLLMServer.instance("prompt tools replace matching rules and preserve existing restrictions", () =>
|
||||
Effect.gen(function* () {
|
||||
const prompt = yield* SessionPrompt.Service
|
||||
const sessions = yield* Session.Service
|
||||
@@ -1068,8 +1070,13 @@ noLLMServer.instance("prompt tools replace previous prompt tool rules", () =>
|
||||
})
|
||||
|
||||
const reloaded = yield* sessions.get(session.id)
|
||||
expect(reloaded.permission).toEqual([{ permission: "read", pattern: "*", action: "allow" }])
|
||||
expect(Permission.evaluate("bash", "anything", reloaded.permission ?? []).action).toBe("ask")
|
||||
// kilocode_change start - Kilo preserves existing restrictions that the new prompt does not override
|
||||
expect(reloaded.permission).toEqual([
|
||||
{ permission: "bash", pattern: "*", action: "deny" },
|
||||
{ permission: "read", pattern: "*", action: "allow" },
|
||||
])
|
||||
expect(Permission.evaluate("bash", "anything", reloaded.permission ?? []).action).toBe("deny")
|
||||
// kilocode_change end
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -489,7 +489,7 @@ describe("tool.task", () => {
|
||||
]),
|
||||
)
|
||||
// kilocode_change end
|
||||
expect(seen?.tools).toBeUndefined({
|
||||
expect(seen?.tools).toEqual({
|
||||
question: false, // kilocode_change - subagents cannot prompt the user directly
|
||||
interactive_terminal: false, // kilocode_change - subagents cannot take over the user's terminal
|
||||
todowrite: false,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"customConditions": ["browser"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@tui/*": ["../tui/src/*"], // kilocode_change - extracted TUI imports used by Kilo-owned components
|
||||
"@test/*": ["./test/*"]
|
||||
}
|
||||
}
|
||||
|
||||
+6654
-1981
File diff suppressed because it is too large
Load Diff
+3754
-1340
File diff suppressed because it is too large
Load Diff
@@ -63,7 +63,7 @@ import * as Model from "./util/model"
|
||||
import { ArgsProvider, useArgs, type Args } from "./context/args"
|
||||
import open from "open"
|
||||
import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
||||
import { TuiConfigProvider, useTuiConfig, type TuiConfig } from "./config"
|
||||
import type { TuiConfig } from "./config"
|
||||
import { createTuiApiAdapters } from "./plugin/adapters"
|
||||
import { createTuiApi } from "./plugin/api"
|
||||
import { createPluginRuntime, PluginRuntimeProvider, usePluginRuntime, type TuiPluginHost } from "./plugin/runtime"
|
||||
@@ -290,7 +290,8 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<TuiConfigProvider config={input.config}>
|
||||
{/* kilocode_change - retain reactive Kilo TUI config hot reload after package extraction */}
|
||||
<KiloApp.KiloTuiConfig.Provider config={input.config}>
|
||||
<PluginRuntimeProvider value={pluginRuntime}>
|
||||
<SDKProvider
|
||||
url={input.url}
|
||||
@@ -331,7 +332,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
</PluginRuntimeProvider>
|
||||
</TuiConfigProvider>
|
||||
</KiloApp.KiloTuiConfig.Provider>
|
||||
</RouteProvider>
|
||||
</ToastProvider>
|
||||
</KVProvider>
|
||||
@@ -362,7 +363,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||
|
||||
function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPluginHost }) {
|
||||
const startup = useTuiStartup()
|
||||
const tuiConfig = useTuiConfig()
|
||||
const tuiConfig = KiloApp.KiloTuiConfig.use() // kilocode_change
|
||||
const route = useRoute()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const renderer = useRenderer()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { RGBA } from "@opentui/core"
|
||||
import { For, type JSX } from "solid-js"
|
||||
import { useTheme, tint } from "@tui/context/theme"
|
||||
import { tui } from "../../../../kilocode/cli/logo"
|
||||
import { tui } from "@/kilocode/cli/logo"
|
||||
|
||||
// Shadow markers (rendered chars in parens):
|
||||
// _ = full shadow cell (space with bg=shadow)
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { Schema } from "effect"
|
||||
import { createContext, type JSX, useContext } from "solid-js"
|
||||
import { TuiKeybind } from "./keybind"
|
||||
import { KiloTitleIcon } from "@/kilocode/cli/cmd/tui/title-icon" // kilocode_change
|
||||
|
||||
export const AttentionSoundName = Schema.Literals([
|
||||
"default",
|
||||
@@ -58,11 +59,13 @@ export const Info = Schema.Struct({
|
||||
plugin_enabled: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
|
||||
leader_timeout: Schema.optional(LeaderTimeout),
|
||||
attention: Schema.optional(Attention),
|
||||
title_icon: Schema.optional(KiloTitleIcon.Value), // kilocode_change
|
||||
prompt: Schema.optional(Prompt),
|
||||
scroll_speed: Schema.optional(ScrollSpeed).annotate({ description: "TUI scroll speed" }),
|
||||
scroll_acceleration: Schema.optional(ScrollAcceleration),
|
||||
diff_style: Schema.optional(DiffStyle),
|
||||
mouse: Schema.optional(Schema.Boolean).annotate({ description: "Enable or disable mouse capture (default: true)" }),
|
||||
vim: Schema.optional(Schema.Boolean), // kilocode_change - retain Kilo prompt editing mode
|
||||
})
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ export function useEvent() {
|
||||
const payload = normalizeSyncEvent(event.payload)
|
||||
if (!payload) return
|
||||
if (event.directory === "global" || event.project === project.project()) {
|
||||
handler(payload, { workspace: event.workspace })
|
||||
handler(payload, { directory: event.directory, workspace: event.workspace })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import type {
|
||||
ConsoleState,
|
||||
BackgroundProcessInfo, // kilocode_change
|
||||
InteractiveTerminalSnapshot, // kilocode_change
|
||||
IndexingStatus, // kilocode_change
|
||||
} from "@kilocode/sdk/v2"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { useProject } from "./project"
|
||||
@@ -38,7 +39,6 @@ import { useKV } from "./kv"
|
||||
import { handleSuggestionEvent } from "@/kilocode/suggestion/tui/sync" // kilocode_change
|
||||
import { appendTerminalOutput } from "@/kilocode/interactive-terminal/output" // kilocode_change
|
||||
import { useToast } from "../ui/toast" // kilocode_change
|
||||
import type { IndexingStatus } from "@kilocode/kilo-indexing/status" // kilocode_change
|
||||
|
||||
const emptyConsoleState: ConsoleState = {
|
||||
consoleManagedProviders: [],
|
||||
@@ -123,6 +123,7 @@ export const {
|
||||
all: [],
|
||||
default: {},
|
||||
connected: [],
|
||||
failed: [],
|
||||
},
|
||||
console_state: emptyConsoleState,
|
||||
provider_auth: {},
|
||||
@@ -159,11 +160,6 @@ export const {
|
||||
const toast = useToast() // kilocode_change
|
||||
|
||||
// kilocode_change start
|
||||
function processScope(scope: string) {
|
||||
const current = project.instance.path()
|
||||
return scope === current.directory || scope === current.worktree || scope === project.data.project.worktree
|
||||
}
|
||||
|
||||
function evict(sessionID: string) {
|
||||
const children = store.session.filter((session) => session.parentID === sessionID).map((session) => session.id)
|
||||
setStore(
|
||||
@@ -402,7 +398,6 @@ export const {
|
||||
|
||||
// kilocode_change start
|
||||
case "background_process.updated": {
|
||||
if (!processScope(event.properties.scope)) break
|
||||
const info = event.properties.info
|
||||
deleted.delete(info.id)
|
||||
setStore(
|
||||
@@ -423,7 +418,6 @@ export const {
|
||||
break
|
||||
}
|
||||
case "background_process.deleted": {
|
||||
if (!processScope(event.properties.scope)) break
|
||||
deleted.add(event.properties.processID)
|
||||
setStore(
|
||||
"background_process",
|
||||
@@ -439,7 +433,6 @@ export const {
|
||||
break
|
||||
}
|
||||
case "interactive_terminal.updated": {
|
||||
if (!processScope(event.properties.scope)) break
|
||||
const info = event.properties.info
|
||||
terminalDeleted.delete(info.id)
|
||||
const list = store.interactive_terminal[info.sessionID] ?? []
|
||||
@@ -626,6 +619,126 @@ export const {
|
||||
}
|
||||
})
|
||||
|
||||
// kilocode_change start - retain versioned Sync events used by Kilo clients
|
||||
event.sync((event) => {
|
||||
switch (event.name) {
|
||||
case "session.created.1": {
|
||||
const info = event.data.info
|
||||
const match = search(store.session, info.id, (item) => item.id)
|
||||
if (match.found) setStore("session", match.index, reconcile(info))
|
||||
if (!match.found)
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => draft.splice(match.index, 0, info)),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "session.updated.1": {
|
||||
const id = event.data.sessionID
|
||||
const match = search(store.session, id, (item) => item.id)
|
||||
if (!match.found) break
|
||||
setStore(
|
||||
"session",
|
||||
match.index,
|
||||
produce((draft) => void Object.assign(draft, event.data.info)),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "session.deleted.1": {
|
||||
const id = event.data.sessionID
|
||||
const match = search(store.session, id, (item) => item.id)
|
||||
if (match.found)
|
||||
setStore(
|
||||
"session",
|
||||
produce((draft) => draft.splice(match.index, 1)),
|
||||
)
|
||||
evict(id)
|
||||
break
|
||||
}
|
||||
case "message.updated.1": {
|
||||
touchMessage(event.data.info.sessionID, event.data.info.id)
|
||||
const info = strip(event.data.info)
|
||||
const messages = store.message[info.sessionID]
|
||||
if (!messages) {
|
||||
setStore("message", info.sessionID, [info])
|
||||
break
|
||||
}
|
||||
const match = search(messages, info.id, (item) => item.id)
|
||||
if (match.found) {
|
||||
setStore("message", info.sessionID, match.index, reconcile(info))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"message",
|
||||
info.sessionID,
|
||||
produce((draft) => draft.splice(match.index, 0, info)),
|
||||
)
|
||||
const updated = store.message[info.sessionID]
|
||||
if (updated.length <= 100) break
|
||||
const oldest = updated[0]
|
||||
batch(() => {
|
||||
setStore(
|
||||
"message",
|
||||
info.sessionID,
|
||||
produce((draft) => draft.shift()),
|
||||
)
|
||||
setStore(
|
||||
"part",
|
||||
produce((draft) => void delete draft[oldest.id]),
|
||||
)
|
||||
})
|
||||
break
|
||||
}
|
||||
case "message.removed.1": {
|
||||
touchMessage(event.data.sessionID, event.data.messageID)
|
||||
const messages = store.message[event.data.sessionID]
|
||||
if (!messages) break
|
||||
const match = search(messages, event.data.messageID, (item) => item.id)
|
||||
if (!match.found) break
|
||||
setStore(
|
||||
"message",
|
||||
event.data.sessionID,
|
||||
produce((draft) => draft.splice(match.index, 1)),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.part.updated.1": {
|
||||
touchPart(event.data.sessionID, event.data.part.id)
|
||||
const part = event.data.part
|
||||
const parts = store.part[part.messageID]
|
||||
if (!parts) {
|
||||
setStore("part", part.messageID, [part])
|
||||
break
|
||||
}
|
||||
const match = search(parts, part.id, (item) => item.id)
|
||||
if (match.found) {
|
||||
setStore("part", part.messageID, match.index, reconcile(part))
|
||||
break
|
||||
}
|
||||
setStore(
|
||||
"part",
|
||||
part.messageID,
|
||||
produce((draft) => draft.splice(match.index, 0, part)),
|
||||
)
|
||||
break
|
||||
}
|
||||
case "message.part.removed.1": {
|
||||
touchPart(event.data.sessionID, event.data.partID)
|
||||
const parts = store.part[event.data.messageID]
|
||||
if (!parts) break
|
||||
const match = search(parts, event.data.partID, (item) => item.id)
|
||||
if (!match.found) break
|
||||
setStore(
|
||||
"part",
|
||||
event.data.messageID,
|
||||
produce((draft) => draft.splice(match.index, 1)),
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
// kilocode_change end
|
||||
|
||||
const exit = useExit()
|
||||
const args = useArgs()
|
||||
|
||||
|
||||
@@ -2700,6 +2700,7 @@ function Question(props: ToolProps) {
|
||||
const { theme } = useTheme()
|
||||
const questions = createMemo(() => parseQuestions(props.input.questions))
|
||||
const answers = createMemo(() => parseQuestionAnswers(props.metadata.answers))
|
||||
const dismissed = createMemo(() => props.metadata.dismissed === true) // kilocode_change
|
||||
const count = createMemo(() => questions().length)
|
||||
|
||||
function format(answer?: ReadonlyArray<string>) {
|
||||
@@ -2711,7 +2712,7 @@ function Question(props: ToolProps) {
|
||||
const title = createMemo(() => (dismissed() ? "# Questions (dismissed)" : "# Questions"))
|
||||
const subtitle = createMemo(() => {
|
||||
if (dismissed()) return `${count()} dismissed`
|
||||
if ((props.metadata.answers?.length ?? 0) > 0) return `${count()} answered`
|
||||
if ((answers()?.length ?? 0) > 0) return `${count()} answered`
|
||||
return `${count()} question${count() !== 1 ? "s" : ""}`
|
||||
})
|
||||
// kilocode_change end
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { Show, createEffect, createSignal, onCleanup } from "solid-js"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { SplitBorder } from "../../component/border"
|
||||
import { SplitBorder } from "../../ui/border"
|
||||
import { useSDK } from "../../context/sdk"
|
||||
import { useDialog } from "../../ui/dialog"
|
||||
import type { SessionNetworkWait } from "@kilocode/sdk/v2"
|
||||
import { useTuiConfig } from "../../context/tui-config"
|
||||
import { useTuiConfig } from "../../config"
|
||||
import { useBindings } from "../../keymap"
|
||||
|
||||
export function NetworkPrompt(props: { request: SessionNetworkWait }) {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// kilocode_change - new file
|
||||
export { SuggestPrompt } from "../../../../../kilocode/suggestion/tui/prompt"
|
||||
export { SuggestPrompt } from "@/kilocode/suggestion/tui/prompt"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TextAttributes, decodePasteBytes, type MouseEvent, type PasteEvent } fr
|
||||
import { useKeyboard, usePaste, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import type { InteractiveTerminalSnapshot } from "@kilocode/sdk/v2"
|
||||
import { VtScreen } from "@/kilocode/cli/cmd/tui/vt/vt-screen"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { SplitBorder } from "@tui/ui/border"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
|
||||
@@ -45,7 +45,7 @@ export interface DialogSelectProps<T> {
|
||||
}
|
||||
|
||||
// kilocode_change start - support list-level actions when no option is selected
|
||||
type DialogSelectActionBase = {
|
||||
type DialogSelectActionBase<T> = {
|
||||
command: string
|
||||
title: string
|
||||
side?: "left" | "right"
|
||||
@@ -53,7 +53,7 @@ type DialogSelectActionBase = {
|
||||
disabled?: boolean | ((option: DialogSelectOption<T> | undefined) => boolean)
|
||||
}
|
||||
|
||||
type DialogSelectAction<T> = DialogSelectActionBase &
|
||||
type DialogSelectAction<T> = DialogSelectActionBase<T> &
|
||||
(
|
||||
| {
|
||||
requiresSelection?: true
|
||||
|
||||
@@ -6,7 +6,8 @@ import { KVProvider, useKV } from "../../../../src/context/kv"
|
||||
import { ProjectProvider, useProject } from "../../../../src/context/project"
|
||||
import { SDKProvider } from "../../../../src/context/sdk"
|
||||
import { SyncProvider, useSync } from "../../../../src/context/sync"
|
||||
import { ToastProvider } from "../../../../src/cli/cmd/tui/ui/toast" // kilocode_change
|
||||
import { ToastProvider } from "../../../../src/ui/toast" // kilocode_change
|
||||
import { ExitProvider } from "../../../../src/context/exit" // kilocode_change
|
||||
import { createEventSource, createFetch, type FetchHandler, directory } from "../../../fixture/tui-sdk"
|
||||
import { TestTuiContexts } from "../../../fixture/tui-environment"
|
||||
export { createEventSource, createFetch, directory, eventSource, json, worktree } from "../../../fixture/tui-sdk"
|
||||
@@ -52,9 +53,13 @@ export async function mount(override?: FetchHandler, state?: string) {
|
||||
{/* kilocode_change end */}
|
||||
<SDKProvider url="http://test" directory={directory} fetch={calls.fetch} events={events.source}>
|
||||
<ProjectProvider>
|
||||
<SyncProvider>
|
||||
<Probe />
|
||||
</SyncProvider>
|
||||
{/* kilocode_change start - SyncProvider consumes the exit context */}
|
||||
<ExitProvider exit={() => {}}>
|
||||
<SyncProvider>
|
||||
<Probe />
|
||||
</SyncProvider>
|
||||
</ExitProvider>
|
||||
{/* kilocode_change end */}
|
||||
</ProjectProvider>
|
||||
</SDKProvider>
|
||||
{/* kilocode_change start */}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* `messages.data!` while the SDK leaves `data` undefined on error.
|
||||
*/
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { disposeAllInstances, provideTestInstance, tmpdir } from "../../../fixture/fixture"
|
||||
import { tmpdir } from "../../../fixture/fixture"
|
||||
import { directory, json, mount } from "./sync-fixture"
|
||||
|
||||
const sessionID = "ses_undef"
|
||||
@@ -25,26 +25,19 @@ describe("tui sync (#26560)", () => {
|
||||
directory,
|
||||
project_id: "proj_test",
|
||||
}
|
||||
// kilocode_change start
|
||||
const { app, sync } = await provideTestInstance({
|
||||
directory: tmp.path,
|
||||
fn: () =>
|
||||
mount((url) => {
|
||||
if (url.pathname === `/session/${sessionID}`) return json(sessionPayload)
|
||||
if (url.pathname === `/session/${sessionID}/messages`) return json({}, { status: 500 })
|
||||
if (url.pathname === `/session/${sessionID}/todo`) return json([])
|
||||
if (url.pathname === `/session/${sessionID}/diff`) return json([])
|
||||
if (url.pathname === "/session") return json([sessionPayload])
|
||||
return undefined
|
||||
}, tmp.path),
|
||||
})
|
||||
// kilocode_change end
|
||||
const { app, sync } = await mount((url) => {
|
||||
if (url.pathname === `/session/${sessionID}`) return json(sessionPayload)
|
||||
if (url.pathname === `/session/${sessionID}/messages`) return json({}, { status: 500 })
|
||||
if (url.pathname === `/session/${sessionID}/todo`) return json([])
|
||||
if (url.pathname === `/session/${sessionID}/diff`) return json([])
|
||||
if (url.pathname === "/session") return json([sessionPayload])
|
||||
return undefined
|
||||
}, tmp.path)
|
||||
|
||||
try {
|
||||
await expect(sync.session.sync(sessionID)).resolves.toBeUndefined()
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
await disposeAllInstances() // kilocode_change
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { disposeAllInstances, provideTestInstance, tmpdir } from "../../../fixture/fixture"
|
||||
import { mount, wait } from "./sync-fixture"
|
||||
import { tmpdir } from "../../../fixture/fixture"
|
||||
import { json, mount, wait } from "./sync-fixture"
|
||||
import type { GlobalEvent } from "@kilocode/sdk/v2"
|
||||
|
||||
function branchEvent(branch: string, workspace?: string): GlobalEvent {
|
||||
@@ -21,7 +21,7 @@ describe("tui sync", () => {
|
||||
test("refresh scopes sessions by default and lists project sessions when disabled", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Bun.write(`${tmp.path}/kv.json`, "{}")
|
||||
const { app, kv, sync, session } = await provideTestInstance({ directory: tmp.path, fn: () => mount(undefined, tmp.path) }) // kilocode_change
|
||||
const { app, kv, sync, session } = await mount(undefined, tmp.path)
|
||||
|
||||
try {
|
||||
expect(kv.get("session_directory_filter_enabled", true)).toBe(true)
|
||||
@@ -35,14 +35,16 @@ describe("tui sync", () => {
|
||||
expect(session.at(-1)?.searchParams.get("path")).toBeNull()
|
||||
} finally {
|
||||
app.renderer.destroy()
|
||||
await disposeAllInstances() // kilocode_change
|
||||
}
|
||||
})
|
||||
|
||||
test("vcs branch updates only apply for the active workspace", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
await Bun.write(`${tmp.path}/kv.json`, "{}")
|
||||
const { app, emit, project, sync } = await mount(undefined, tmp.path)
|
||||
const { app, emit, project, sync } = await mount(
|
||||
(url) => (url.pathname === "/experimental/workspace" ? json([{ id: "ws_a" }]) : undefined),
|
||||
tmp.path,
|
||||
) // kilocode_change - workspace re-bootstrap retains the selected test workspace
|
||||
|
||||
try {
|
||||
expect(sync.data.vcs?.branch).toBe("main")
|
||||
|
||||
@@ -54,8 +54,18 @@ export function createFetch(override?: FetchHandler) {
|
||||
].includes(url.pathname)
|
||||
)
|
||||
return json([])
|
||||
if (["/config", "/experimental/resource", "/mcp", "/provider/auth", "/session/status"].includes(url.pathname))
|
||||
if (
|
||||
["/config", "/global/config", "/experimental/resource", "/mcp", "/provider/auth", "/session/status"].includes(
|
||||
url.pathname,
|
||||
)
|
||||
)
|
||||
return json({})
|
||||
// kilocode_change start - Kilo bootstrap endpoints
|
||||
if (["/network", "/background-process", "/interactive-terminal", "/config/warnings"].includes(url.pathname))
|
||||
return json([])
|
||||
if (url.pathname === "/indexing/status")
|
||||
return json({ state: "Disabled", message: "Indexing disabled.", processedFiles: 0, totalFiles: 0, percent: 0 })
|
||||
// kilocode_change end
|
||||
if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
|
||||
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
|
||||
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
|
||||
|
||||
@@ -5,6 +5,13 @@
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "@opentui/solid",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"noUncheckedIndexedAccess": false
|
||||
}
|
||||
"noUncheckedIndexedAccess": false,
|
||||
// kilocode_change - resolve Kilo integrations retained during upstream's standalone TUI extraction
|
||||
"paths": {
|
||||
"@/*": ["../opencode/src/*"],
|
||||
"@tui/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
// kilocode_change - include ambient declarations used by retained Kilo imports from opencode
|
||||
"include": ["src", "test", "../opencode/src/*.d.ts"]
|
||||
}
|
||||
|
||||
@@ -24,11 +24,12 @@ const allow: Record<string, string> = {
|
||||
"cli/cmd/run/runtime.boot.ts": "direct run startup resolver runtime boundary",
|
||||
"cli/cmd/run/stream.transport.ts": "per-subscription direct run transport runtime boundary",
|
||||
"cli/cmd/run/variant.shared.ts": "direct run variant persistence runtime boundary with test filesystem injection",
|
||||
"cli/cmd/tui/config/tui.ts": "separately tracked TUI config facade",
|
||||
"config/tui.ts": "separately tracked TUI config facade moved by the upstream TUI extraction",
|
||||
"installation/index.ts": "existing installation facade outside #10655",
|
||||
}
|
||||
|
||||
const testAllow: Record<string, { count: number; reason: string }> = {
|
||||
"preload.ts": { count: 2, reason: "global test-suite AppRuntime cleanup boundary" },
|
||||
"kilocode/config-resilience.test.ts": { count: 4, reason: "existing runtime integration test" },
|
||||
"kilocode/config-validation.test.ts": { count: 2, reason: "existing runtime integration test" },
|
||||
"kilocode/cli-shutdown.test.ts": { count: 1, reason: "mocked runtime boundary for shutdown unit tests" },
|
||||
|
||||
Reference in New Issue
Block a user