chore(cli): normalize upstream annotations

This commit is contained in:
marius-kilocode
2026-06-08 12:45:47 +02:00
parent cf08de3f44
commit 5706abce76
37 changed files with 315 additions and 267 deletions
+6 -6
View File
@@ -101,7 +101,7 @@ export const layer = Layer.effect(
} satisfies Record<string, "allow" | "ask" | "deny">
const baseDefaults = Permission.fromConfig({
// kilocode_change: renamed from defaults
// kilocode_change
"*": "allow",
doom_loop: "ask",
external_directory: {
@@ -288,9 +288,7 @@ export const layer = Layer.effect(
// kilocode_change start - rename build→code, add debug/orchestrator/ask, patch plan/explore
KiloAgent.patchAgents(agents, defaults, user, cfg, kilo, ctx.worktree, whitelistedDirs)
// kilocode_change end
// kilocode_change start - preprocess config to remap "build" key → "code"
const agentConfigs = KiloAgent.preprocessConfig(cfg.agent ?? {})
for (const [key, value] of Object.entries(agentConfigs)) {
// kilocode_change end
@@ -347,7 +345,7 @@ export const layer = Layer.effect(
`Repository: ${reference.repository}`,
...(reference.branch ? [`Branch/ref: ${reference.branch}`] : []),
`Cached directory: ${reference.path}`,
`OpenCode materializes this configured repository before use. Do not call repo_clone for this reference.`,
`OpenCode 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")
@@ -428,8 +426,10 @@ export const layer = Layer.effect(
const defaultAgent = Effect.fnUntraced(function* () {
const c = yield* config.get()
if (c.default_agent) {
const effective = KiloAgent.resolveKey(c.default_agent) // kilocode_change - treat "build" as "code"
const agent = agents[effective] // kilocode_change
// 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`)
+11 -13
View File
@@ -1,4 +1,4 @@
import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
import { render, TimeToFirstDraw, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid" // kilocode_change
import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
import * as Clipboard from "@tui/util/clipboard"
import * as Selection from "@tui/util/selection"
@@ -52,8 +52,10 @@ import { DialogConfirm } from "./ui/dialog-confirm"
import { ToastProvider, useToast } from "./ui/toast"
import { ExitProvider, useExit } from "./context/exit"
import { Session as SessionApi } from "@/session/session"
// kilocode_change start
import { DialogSelect } from "./ui/dialog-select"
import { Link } from "./ui/link"
// kilocode_change end
import { TuiEvent } from "./event"
import { KVProvider, useKV } from "./context/kv"
import { Provider } from "@/provider/provider"
@@ -176,7 +178,6 @@ export function tui(input: {
await TuiPluginRuntime.dispose()
}
// kilocode_change - safety net: ensure mouse tracking is disabled regardless of exit path
process.on("exit", resetTerminalState) // kilocode_change
const renderer = await createCliRenderer(rendererConfig(input.config))
@@ -190,11 +191,9 @@ export function tui(input: {
await render(() => {
return (
<ErrorBoundary
// kilocode_change start
fallback={(error, reset) => (
<ErrorComponent error={error} reset={reset} onBeforeExit={onBeforeExit} onExit={onExit} mode={mode} />
)}
// kilocode_change end
>
<OpencodeKeymapProvider keymap={keymap}>
<ArgsProvider {...input.args}>
@@ -336,8 +335,10 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
kv.get("paste_summary_enabled", !sync.data.config.experimental?.disable_paste_summary),
)
KiloApp.useSessionEffects({ route, sdk, sync }) // kilocode_change
KiloApp.useTuiConfigHotReload() // kilocode_change - hot reload TUI keybinds/theme/ui settings
// kilocode_change start
KiloApp.useSessionEffects({ route, sdk, sync })
KiloApp.useTuiConfigHotReload()
// kilocode_change end
// Update terminal window title based on current route and session
createEffect(() => {
@@ -346,24 +347,24 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
const titleDefault = KiloApp.APP_TITLE // kilocode_change
if (route.data.type === "home") {
renderer.setTerminalTitle(titleDefault)
renderer.setTerminalTitle(titleDefault) // kilocode_change
return
}
if (route.data.type === "session") {
const session = sync.session.get(route.data.sessionID)
if (!session || SessionApi.isDefaultTitle(session.title)) {
renderer.setTerminalTitle(titleDefault)
renderer.setTerminalTitle(titleDefault) // kilocode_change
return
}
const title = session.title.length > 40 ? session.title.slice(0, 37) + "..." : session.title
renderer.setTerminalTitle(`${titleDefault} | ${title}`)
renderer.setTerminalTitle(`${titleDefault} | ${title}`) // kilocode_change
return
}
if (route.data.type === "plugin") {
renderer.setTerminalTitle(`${titleDefault} | ${route.data.id}`)
renderer.setTerminalTitle(`${titleDefault} | ${route.data.id}`) // kilocode_change
}
// kilocode_change start
@@ -984,9 +985,7 @@ function tryUseTerminalDimensions() {
return undefined
}
}
// kilocode_change end
// kilocode_change start — inlined ErrorComponent with safe renderer/keyboard guards
function ErrorComponent(props: {
error: Error
reset: () => void
@@ -1004,7 +1003,6 @@ function ErrorComponent(props: {
renderer?.setTerminalTitle("")
renderer?.destroy()
win32FlushInputBuffer()
// kilocode_change - reset terminal state to disable mouse tracking on exit
resetTerminalState()
await props.onExit()
}
@@ -57,8 +57,10 @@ import {
} from "../dialog-workspace-create"
import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable"
import { useArgs } from "@tui/context/args"
import { KiloSessionTuiSync } from "@/kilocode/session/tui-sync" // kilocode_change
import { slashMatches } from "@/kilocode/cli/cmd/command-display" // kilocode_change
// kilocode_change start
import { KiloSessionTuiSync } from "@/kilocode/session/tui-sync"
import { slashMatches } from "@/kilocode/cli/cmd/command-display"
// kilocode_change end
import { Flag } from "@opencode-ai/core/flag/flag"
import { type WorkspaceStatus } from "../workspace-label"
import { useCommandPalette } from "../../context/command-palette"
@@ -391,11 +393,9 @@ export function Prompt(props: PromptProps) {
const sessionID = props.sessionID
const msg = lastUserMessage()
if (!sessionID || !msg) return
// kilocode_change start - skip compaction messages while syncing local agent/model
const parts = sync.data.part[msg.id]
if (!parts) return
if (!KiloSessionTuiSync.model({ role: msg.role, parts })) return
// kilocode_change end
const key = [sessionID, msg.id].join(":")
if (key === syncedKey) return
@@ -1534,12 +1534,12 @@ export function Prompt(props: PromptProps) {
syncExtmarksWithPromptParts()
setCursorVersion((value) => value + 1)
}}
/* kilocode_change start */
onCursorChange={() => {
setCursorVersion((value) => value + 1)
// kilocode_change start - dismiss autocomplete when cursor movement invalidates its filter
if (store.mode === "normal") auto()?.onCursorChange()
// kilocode_change end
}}
/* kilocode_change end */
onKeyDown={(e: { preventDefault(): void }) => {
if (props.disabled) {
e.preventDefault()
@@ -1641,7 +1641,9 @@ export function Prompt(props: PromptProps) {
</box>
<box
height={1}
flexShrink={0} // kilocode_change - prevent border box from shrinking in narrow terminals (#6309)
/* kilocode_change start */
flexShrink={0}
/* kilocode_change end */
border={["left"]}
borderColor={borderHighlight()}
customBorderChars={{
@@ -23,8 +23,10 @@ import { SplitBorder } from "@tui/component/border"
import { Spinner } from "@tui/component/spinner"
import { selectedForeground, useTheme } from "@tui/context/theme"
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core"
import type { KeyEvent } from "@opentui/core" // kilocode_change
import type { CommandContext } from "@opentui/keymap" // kilocode_change
// kilocode_change start
import type { KeyEvent } from "@opentui/core"
import type { CommandContext } from "@opentui/keymap"
// kilocode_change end
import { Prompt, type PromptRef } from "@tui/component/prompt"
// kilocode_change start
import type { AssistantMessage, Part, Provider, ToolPart, UserMessage, TextPart, ReasoningPart } from "@kilocode/sdk/v2"
@@ -80,21 +82,25 @@ import { useExit } from "../../context/exit"
import { Filesystem } from "@/util/filesystem"
import { PermissionPrompt } from "./permission"
import { QuestionPrompt } from "./question"
import { Suggest } from "@/kilocode/suggestion/tui/render" // kilocode_change
import { SuggestPrompt } from "@/kilocode/suggestion/tui/prompt" // kilocode_change
import { NetworkPrompt } from "./network" // kilocode_change
// kilocode_change start
import { Suggest } from "@/kilocode/suggestion/tui/render"
import { SuggestPrompt } from "@/kilocode/suggestion/tui/prompt"
import { NetworkPrompt } from "./network"
// kilocode_change end
import { DialogExportOptions } from "../../ui/dialog-export-options"
import * as Model from "../../util/model"
import { formatTranscript } from "../../util/transcript"
import { UI } from "@/cli/ui.ts"
import { useTuiConfig } from "../../context/tui-config"
import { splitDiffHunks } from "@/kilocode/tui/diff" // kilocode_change
import { session as banner } from "@/kilocode/cli/logo" // kilocode_change
// kilocode_change start
import { splitDiffHunks } from "@/kilocode/tui/diff"
import { session as banner } from "@/kilocode/cli/logo"
import { formatMarkdownTables } from "../../util/markdown" // kilocode_change
import { bell } from "@/kilocode/bell" // kilocode_change
import { SessionIndexing } from "@/kilocode/components/session-indexing" // kilocode_change
import { submitFeedback } from "@/kilocode/cli/cmd/tui/feedback" // kilocode_change
import { formatMarkdownTables } from "../../util/markdown"
import { bell } from "@/kilocode/bell"
import { SessionIndexing } from "@/kilocode/components/session-indexing"
import { submitFeedback } from "@/kilocode/cli/cmd/tui/feedback"
// kilocode_change end
import { getScrollAcceleration } from "../../util/scroll"
import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime"
import { DialogRetryAction } from "../../component/dialog-retry-action"
@@ -229,10 +235,8 @@ export function Session() {
const nonBlockingQuestions = createMemo(() => questions().filter((q) => q.blocking === false))
const question = createMemo(() => blockingQuestions()[0] ?? nonBlockingQuestions()[0])
const blockingSuggestions = createMemo(() => suggestions().filter((s) => s.blocking !== false))
// kilocode_change start - footer overlay only hosts blocking suggestions now;
// non-blocking ones render inline at the tool-part slot via `SuggestBar`.
const blockingSuggestion = createMemo(() => blockingSuggestions()[0])
// kilocode_change end
const visible = createMemo(
() =>
!session()?.parentID &&
@@ -263,9 +267,7 @@ export function Session() {
const lastAssistant = createMemo(() => {
return messages().findLast((x) => x.role === "assistant")
})
// kilocode_change end
// kilocode_change start - ring terminal bell on task completion
createEffect(
on(
() => [route.sessionID, sync.data.session_status?.[route.sessionID]?.type] as const,
@@ -275,9 +277,7 @@ export function Session() {
},
),
)
// kilocode_change end
// kilocode_change start - ring terminal bell when input is needed
createEffect(
on(
() => [route.sessionID, permissions().length] as const,
@@ -298,7 +298,7 @@ export function Session() {
)
createEffect(
on(
() => [route.sessionID, suggestions().length + network().length] as const, // kilocode_change
() => [route.sessionID, suggestions().length + network().length] as const,
([id, len], prev) => {
if (!prev || prev[0] !== id) return
if (len > prev[1] && bellEnabled()) bell()
@@ -423,8 +423,8 @@ export function Session() {
if (part.state.status !== "completed") return
if (part.id === lastSwitch) return
// kilocode_change - plan_exit no longer switches agent; PlanFollowup handles it
if (part.tool === "plan_enter") {
// kilocode_change
local.agent.set("plan")
lastSwitch = part.id
}
@@ -471,9 +471,7 @@ export function Session() {
const title = Locale.truncate(session()?.title ?? "", 50)
return exit.message.set(banner(title, session()?.id, UI.Style.TEXT_DIM, UI.Style.TEXT_NORMAL))
})
// kilocode_change end
// kilocode_change start - double ctrl+c to exit for child sessions
const [exitPress, setExitPress] = createSignal(0)
useBindings(() => ({
enabled: Boolean(session()?.parentID),
@@ -1406,8 +1404,6 @@ export function Session() {
)}
</Show>
<Show when={permissions().length === 0 && !question()}>
{/* kilocode_change end */}
{/* kilocode_change start */}
<Show when={blockingSuggestion()} keyed>
{(request) => <SuggestPrompt request={request} />}
</Show>
@@ -1415,13 +1411,9 @@ export function Session() {
<Show when={session()?.parentID}>
<SubagentFooter />
</Show>
{/* kilocode_change end */}
{/* kilocode_change start */}
<Show when={networkVisible()}>
<NetworkPrompt request={network()[0]} />
</Show>
{/* kilocode_change end */}
{/* kilocode_change start */}
<Show when={!session()?.parentID}>
<TuiPluginRuntime.Slot
name="session_prompt"
@@ -16,9 +16,11 @@ import { webSearchProviderLabel } from "@/tool/websearch"
import { useDialog } from "../../ui/dialog"
import { getScrollAcceleration } from "../../util/scroll"
import { useTuiConfig } from "../../context/tui-config"
import { ConfigProtection } from "@/kilocode/permission/config-paths" // kilocode_change
import { splitDiffHunks } from "@/kilocode/tui/diff" // kilocode_change
import { normalizeUrls } from "@/kilocode/util/url" // kilocode_change
// kilocode_change start
import { ConfigProtection } from "@/kilocode/permission/config-paths"
import { splitDiffHunks } from "@/kilocode/tui/diff"
import { normalizeUrls } from "@/kilocode/util/url"
// kilocode_change end
import { useBindings, useCommandShortcut } from "../../keymap"
import { usePathFormatter } from "../../context/path-format"
@@ -162,13 +164,15 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
body={
<Switch>
<Match when={props.request.always.length === 1 && props.request.always[0] === "*"}>
{/* kilocode_change */}
{/* kilocode_change start */}
<TextBody title={"This will allow " + props.request.permission + " permanently."} />
{/* kilocode_change end */}
</Match>
<Match when={true}>
<box paddingLeft={1} gap={1}>
{/* kilocode_change */}
{/* kilocode_change start */}
<text fg={theme.textMuted}>This will allow the following patterns permanently</text>
{/* kilocode_change end */}
<box>
<For each={props.request.always}>
{(pattern) => (
@@ -428,13 +432,13 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
</text>
<text fg={theme.text}>{current.title}</text>
</box>
{/* // kilocode_change start - explain config file edits always require approval */}
{/* kilocode_change start - explain config file edits always require approval */}
<Show when={props.request.metadata?.[ConfigProtection.DISABLE_ALWAYS_KEY]}>
<box paddingLeft={4} flexShrink={0}>
<text fg={theme.textMuted}>Config file edits always require approval</text>
</box>
</Show>
{/* // kilocode_change end */}
{/* kilocode_change end */}
</box>
)
@@ -449,7 +453,9 @@ export function PermissionPrompt(props: { request: PermissionRequest }) {
title="Permission required"
header={header()}
body={current.body}
/* kilocode_change start */
options={options}
/* kilocode_change end */
escapeKey="reject"
fullscreen
onSelect={(option) => {
@@ -529,7 +535,6 @@ function RejectPrompt(props: { onConfirm: (message: string) => void; onCancel: (
<text fg={theme.text}>Reject permission</text>
</box>
<box paddingLeft={1}>
{/* kilocode_change */}
<text fg={theme.textMuted}>Tell Kilo what to do differently</text>
</box>
</box>
+26 -26
View File
@@ -175,7 +175,7 @@ export const Info = Schema.Struct({
description: "Server configuration for the kilo serve command", // kilocode_change
}),
command: Schema.optional(Schema.Record(Schema.String, ConfigCommand.Info)).annotate({
description: "Command configuration, see https://opencode.ai/docs/commands",
description: "Command configuration, see https://opencode.ai/docs/commands", // kilocode_change
}),
skills: Schema.optional(ConfigSkills.Info).annotate({ description: "Additional skill folder paths" }),
reference: Schema.optional(ConfigReference.Info).annotate({
@@ -219,12 +219,10 @@ export const Info = Schema.Struct({
auto_collapse_reasoning: Schema.optional(Schema.Boolean).annotate({
description: "Automatically collapse reasoning blocks after the agent finishes writing them",
}),
indexing: Schema.optional(IndexingRef).annotate({ description: "Codebase indexing configuration" }), // kilocode_change
indexing: Schema.optional(IndexingRef).annotate({ description: "Codebase indexing configuration" }),
terminal_command_display: Schema.optional(Schema.Literals(["expanded", "collapsed"])).annotate({
description: "Controls whether terminal command blocks are expanded or collapsed by default in the VS Code chat UI",
}),
// kilocode_change end
// kilocode_change start - nullable for delete sentinel
model: Schema.optional(Schema.NullOr(ConfigModelID)).annotate({
description: "Model to use in the format of provider/model, eg anthropic/claude-2",
}),
@@ -238,8 +236,6 @@ export const Info = Schema.Struct({
subagent_variant: Schema.optional(Schema.NullOr(Schema.String)).annotate({
description: "Default model variant for task-tool subagents when subagent_model is configured.",
}),
// kilocode_change end
// kilocode_change start - renamed from "build" to "code" + nullable for delete sentinel
default_agent: Schema.optional(Schema.NullOr(Schema.String)).annotate({
description:
"Default agent to use when none is specified. Must be a primary agent. Falls back to 'code' if not set or if the specified agent is invalid.",
@@ -263,9 +259,11 @@ export const Info = Schema.Struct({
// primary
plan: Schema.optional(ConfigAgent.Info),
build: Schema.optional(ConfigAgent.Info),
debug: Schema.optional(ConfigAgent.Info), // kilocode_change
orchestrator: Schema.optional(ConfigAgent.Info), // kilocode_change
ask: Schema.optional(ConfigAgent.Info), // kilocode_change
// kilocode_change start
debug: Schema.optional(ConfigAgent.Info),
orchestrator: Schema.optional(ConfigAgent.Info),
ask: Schema.optional(ConfigAgent.Info),
// kilocode_change end
// subagent
general: Schema.optional(ConfigAgent.Info),
explore: Schema.optional(ConfigAgent.Info),
@@ -277,9 +275,10 @@ export const Info = Schema.Struct({
}),
[Schema.Record(Schema.String, ConfigAgent.Info)],
),
// kilocode_change start
).annotate({ description: "Agent configuration, see https://opencode.ai/docs/agents" }),
provider: Schema.optional(Schema.Record(Schema.String, Schema.NullOr(ConfigProvider.Info))).annotate({
// kilocode_change - nullable for delete sentinel
// kilocode_change end
description: "Custom provider configurations and model overrides",
}),
mcp: Schema.optional(
@@ -358,13 +357,11 @@ export const Info = Schema.Struct({
Schema.Struct({
disable_paste_summary: Schema.optional(Schema.Boolean),
batch_tool: Schema.optional(Schema.Boolean).annotate({ description: "Enable the batch tool" }),
codebase_search: Schema.optional(Schema.Boolean).annotate({ description: "Enable AI-powered codebase search" }), // kilocode_change
// kilocode_change start
codebase_search: Schema.optional(Schema.Boolean).annotate({ description: "Enable AI-powered codebase search" }),
speech_to_text_model: Schema.optional(Schema.String).annotate({
description: "Speech-to-text transcription model ID to use for voice input",
}),
// kilocode_change end
// kilocode_change start - enable telemetry by default
openTelemetry: Schema.Boolean.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(true))).annotate({
description: "Enable telemetry. Set to false to opt-out.",
}),
@@ -412,10 +409,12 @@ export interface Interface {
readonly getGlobal: () => Effect.Effect<Info>
readonly getConsoleState: () => Effect.Effect<ConsoleState>
readonly update: (config: Info) => Effect.Effect<void>
// kilocode_change start
readonly updateGlobal: (
config: Info,
options?: { dispose?: boolean },
) => Effect.Effect<{ info: Info; changed: boolean }> // kilocode_change
) => Effect.Effect<{ info: Info; changed: boolean }>
// kilocode_change end
readonly invalidate: () => Effect.Effect<void>
readonly directories: () => Effect.Effect<string[]>
readonly waitForDependencies: () => Effect.Effect<void>
@@ -438,8 +437,8 @@ function globalConfigFile() {
function patchJsonc(input: string, patch: unknown, path: string[] = []): string {
if (!isRecord(patch)) {
// kilocode_change - null means "delete this key" — pass undefined to jsonc-parser's modify()
const edits = modify(input, path, patch === null ? undefined : patch, {
// kilocode_change
formattingOptions: {
insertSpaces: true,
tabSize: 2,
@@ -519,8 +518,10 @@ export const layer = Layer.effect(
yield* Effect.promise(() => resolveLoadedPlugins(data, options.path))
if (!data.$schema) {
data.$schema = "https://app.kilo.ai/config.json" // kilocode_change
const updated = text.replace(/^\s*\{/, '{\n "$schema": "https://app.kilo.ai/config.json",') // kilocode_change
// kilocode_change start
data.$schema = "https://app.kilo.ai/config.json"
const updated = text.replace(/^\s*\{/, '{\n "$schema": "https://app.kilo.ai/config.json",')
// kilocode_change end
yield* fs.writeFileString(options.path, updated).pipe(Effect.catch(() => Effect.void))
}
return data
@@ -536,8 +537,10 @@ export const layer = Layer.effect(
let globalStamp = "" // kilocode_change
const loadGlobal = Effect.fnUntraced(function* () {
yield* Effect.promise(() => KilocodeConfig.migrateBashPermission()) // kilocode_change
globalStamp = yield* KilocodeGlobalConfigStamp.read(fs, Global.Path.config) // kilocode_change
// kilocode_change start
yield* Effect.promise(() => KilocodeConfig.migrateBashPermission())
globalStamp = yield* KilocodeGlobalConfigStamp.read(fs, Global.Path.config)
// kilocode_change end
let result: Info = {}
result = mergeConfig(result, yield* loadFile(path.join(Global.Path.config, "config.json")))
// kilocode_change start
@@ -686,8 +689,8 @@ export const layer = Layer.effect(
for (const [key, value] of Object.entries(auth)) {
if (value.type === "wellknown") {
const url = key.replace(/\/+$/, "")
const source = `${url}/.well-known/opencode` // kilocode_change
// kilocode_change start - warn instead of fail on wellknown errors
// kilocode_change start
const source = `${url}/.well-known/opencode`
yield* Effect.gen(function* () {
process.env[value.key] = value.token
log.debug("fetching remote config", { url: `${url}/.well-known/opencode` })
@@ -717,7 +720,7 @@ export const layer = Layer.effect(
})) as Record<string, unknown>)
: {}
const remoteConfig = mergeConfig(wellknown.config ?? {}, fetchedConfig as Info)
if (!remoteConfig.$schema) remoteConfig.$schema = "https://app.kilo.ai/config.json" // kilocode_change
if (!remoteConfig.$schema) remoteConfig.$schema = "https://app.kilo.ai/config.json"
const next = yield* loadConfig(JSON.stringify(remoteConfig), {
dir: path.dirname(source),
source,
@@ -1051,10 +1054,8 @@ export const layer = Layer.effect(
},
}),
)
// kilocode_change end
})
// kilocode_change start
const warnings = Effect.fn("Config.warnings")(function* () {
return yield* InstanceState.use(state, (s) => s.warnings)
})
@@ -1076,8 +1077,7 @@ export const layer = Layer.effect(
let changed: boolean
if (!file.endsWith(".jsonc")) {
const existing = ConfigParse.effectSchema(Info, ConfigParse.jsonc(before, file), file)
// kilocode_change - use `patch` (writableGlobal) so empty-string sentinels are stripped via undefined
const merged = KilocodeConfig.mergeConfig(writable(existing), patch)
const merged = KilocodeConfig.mergeConfig(writable(existing), patch) // kilocode_change
const serialized = JSON.stringify(merged, null, 2)
changed = serialized !== before
if (changed) yield* fs.writeFileString(file, serialized).pipe(Effect.orDie)
@@ -1,4 +1,3 @@
// kilocode_change - new file
import { remapChildren as _remapChildren } from "./fork"
import z from "zod"
import { Cause, Effect, Schema } from "effect"
@@ -111,7 +111,6 @@ export namespace Suggestion {
),
}
// kilocode_change - Instance.state() removed in v1.4.4; use module-level state
// (request IDs are globally unique so instance scoping is not needed)
const pending: Record<
string,
+29 -29
View File
@@ -17,10 +17,12 @@ import os from "os"
import z from "zod" // kilocode_change
import { evaluate as evalRule } from "./evaluate"
import { PermissionID } from "./schema"
import { ConfigProtection } from "@/kilocode/permission/config-paths" // kilocode_change
import { drainCovered } from "@/kilocode/permission/drain" // kilocode_change
import { ReadPermission } from "@/kilocode/permission/read" // kilocode_change
import { ExternalDirectoryPermission } from "@/kilocode/permission/external-directory" // kilocode_change
// kilocode_change start
import { ConfigProtection } from "@/kilocode/permission/config-paths"
import { drainCovered } from "@/kilocode/permission/drain"
import { ReadPermission } from "@/kilocode/permission/read"
import { ExternalDirectoryPermission } from "@/kilocode/permission/external-directory"
// kilocode_change end
const log = Log.create({ service: "permission" })
@@ -152,16 +154,20 @@ export interface Interface {
readonly ask: (input: AskInput) => Effect.Effect<void, Error>
readonly reply: (input: ReplyInput) => Effect.Effect<boolean> // kilocode_change
readonly list: () => Effect.Effect<ReadonlyArray<Request>>
readonly saveAlwaysRules: (input: z.infer<typeof SaveAlwaysRulesInput>) => Effect.Effect<boolean> // kilocode_change
readonly allowEverything: (input: z.infer<typeof AllowEverythingInput>) => Effect.Effect<void> // kilocode_change
readonly pending: (id: string) => Effect.Effect<Request | undefined> // kilocode_change
// kilocode_change start
readonly saveAlwaysRules: (input: z.infer<typeof SaveAlwaysRulesInput>) => Effect.Effect<boolean>
readonly allowEverything: (input: z.infer<typeof AllowEverythingInput>) => Effect.Effect<void>
readonly pending: (id: string) => Effect.Effect<Request | undefined>
// kilocode_change end
}
interface PendingEntry {
info: Request
ruleset: Ruleset // kilocode_change
hardRuleset?: Ruleset // kilocode_change
saved?: boolean // kilocode_change
// kilocode_change start
ruleset: Ruleset
hardRuleset?: Ruleset
saved?: boolean
// kilocode_change end
deferred: Deferred.Deferred<void, RejectedError | CorrectedError>
}
@@ -189,9 +195,7 @@ export function resolve(permission: string, pattern: string, ruleset: Ruleset, .
if (saved.action === "allow") return saved
return base
}
// kilocode_change end
// kilocode_change start
function veto(permission: string, pattern: string, ruleset?: Ruleset) {
if (!ruleset) return false
return ExternalDirectoryPermission.evaluate(permission, pattern, ruleset).action === "deny"
@@ -243,9 +247,11 @@ export const layer = Layer.effect(
const ask = Effect.fn("Permission.ask")(function* (input: AskInput) {
const { approved, pending } = yield* InstanceState.get(state)
const { ruleset, hardRuleset, ...request } = input // kilocode_change
const s = yield* InstanceState.get(state) // kilocode_change
const local = s.session[request.sessionID] ?? [] // kilocode_change
// kilocode_change start
const { ruleset, hardRuleset, ...request } = input
const s = yield* InstanceState.get(state)
const local = s.session[request.sessionID] ?? []
// kilocode_change end
let needsAsk = false
// kilocode_change start — force "ask" for config file edits
@@ -332,7 +338,7 @@ export const layer = Layer.effect(
if (input.reply === "once") return true // kilocode_change
// kilocode_change start — downgrade "always" to "once" for config file edits
if (ConfigProtection.isRequest(existing.info)) return true // kilocode_change
if (ConfigProtection.isRequest(existing.info)) return true
// kilocode_change end
for (const pattern of existing.info.always) {
@@ -344,14 +350,10 @@ export const layer = Layer.effect(
action: "allow",
})
}
// kilocode_change end
}
// kilocode_change start — drain covered permissions across sibling Agent Manager sessions
yield* drainCovered(pending as unknown as Map<string, PendingEntry>, approved, DeniedError)
// kilocode_change end
// kilocode_change start — persist always-rules to global config
if (!existing.saved) {
const alwaysRules: Ruleset = existing.info.always.map((pattern) => ({
permission: existing.info.permission,
@@ -362,8 +364,7 @@ export const layer = Layer.effect(
yield* config.updateGlobal({ permission: toConfig(alwaysRules) }, { dispose: false })
}
}
// kilocode_change end
return true // kilocode_change
return true
})
const list = Effect.fn("Permission.list")(function* () {
@@ -371,15 +372,14 @@ export const layer = Layer.effect(
return Array.from(pending.values(), (item) => item.info)
})
// kilocode_change start
const saveAlwaysRules = Effect.fn("Permission.saveAlwaysRules")(function* (
input: z.infer<typeof SaveAlwaysRulesInput>,
) {
const s = yield* InstanceState.get(state)
const existing = s.pending.get(input.requestID)
if (!existing) return false // kilocode_change
if (!existing) return false
if (ConfigProtection.isRequest(existing.info)) return true // kilocode_change
if (ConfigProtection.isRequest(existing.info)) return true
const validRules = new Set([
...((existing.info.metadata?.rules as string[] | undefined) ?? []),
@@ -395,7 +395,7 @@ export const layer = Layer.effect(
if (deniedSet.has(pattern)) newRules.push({ permission, pattern, action: "deny" })
}
s.approved.push(...newRules)
existing.saved = true // kilocode_change
existing.saved = true
if (newRules.length > 0) {
yield* config.updateGlobal({ permission: toConfig(newRules) }, { dispose: false })
@@ -431,7 +431,7 @@ export const layer = Layer.effect(
if (input.requestID) {
const entry = s.pending.get(input.requestID)
const ok = entry ? covered(entry, s.approved, s.session[entry.info.sessionID] ?? []) : false // kilocode_change
const ok = entry ? covered(entry, s.approved, s.session[entry.info.sessionID] ?? []) : false
if (entry && ok && (!input.sessionID || entry.info.sessionID === input.sessionID)) {
s.pending.delete(input.requestID)
yield* bus.publish(Event.Replied, {
@@ -445,7 +445,7 @@ export const layer = Layer.effect(
for (const [id, entry] of s.pending) {
if (input.sessionID && entry.info.sessionID !== input.sessionID) continue
if (!covered(entry, s.approved, s.session[entry.info.sessionID] ?? [])) continue // kilocode_change
if (!covered(entry, s.approved, s.session[entry.info.sessionID] ?? [])) continue
s.pending.delete(id)
yield* bus.publish(Event.Replied, {
sessionID: entry.info.sessionID,
@@ -462,7 +462,7 @@ export const layer = Layer.effect(
})
// kilocode_change end
return Service.of({ ask, reply, list, saveAlwaysRules, allowEverything, pending })
return Service.of({ ask, reply, list, saveAlwaysRules, allowEverything, pending }) // kilocode_change
}),
)
+12 -6
View File
@@ -8,8 +8,10 @@ import * as Vcs from "./vcs"
import { Bus } from "../bus"
import { InstanceState } from "@/effect/instance-state"
import { FileWatcher } from "@/file/watcher"
import { KilocodeBootstrap } from "@/kilocode/bootstrap" // kilocode_change
// import { ShareNext } from "@/share/share-next" // kilocode_change - handled by KilocodeBootstrap
// kilocode_change start
import { KilocodeBootstrap } from "@/kilocode/bootstrap"
// import { ShareNext } from "@/share/share-next"
// kilocode_change end
import { Effect, Layer } from "effect"
import { Config } from "@/config/config"
import { Service } from "./bootstrap-service"
@@ -32,8 +34,10 @@ export const layer = Layer.effect(
const plugin = yield* Plugin.Service
const project = yield* Project.Service
const reference = yield* Reference.Service
const kilocode = yield* KilocodeBootstrap.Service // kilocode_change - Kilo session bootstrap replaces ShareNext
// const shareNext = yield* ShareNext.Service // kilocode_change - handled by KilocodeBootstrap
// kilocode_change start
const kilocode = yield* KilocodeBootstrap.Service
// const shareNext = yield* ShareNext.Service
// kilocode_change end
const snapshot = yield* Snapshot.Service
const vcs = yield* Vcs.Service
@@ -69,8 +73,10 @@ export const defaultLayer: Layer.Layer<Service> = layer.pipe(
Plugin.defaultLayer,
Project.defaultLayer,
Reference.defaultLayer,
KilocodeBootstrap.defaultLayer, // kilocode_change - Kilo session bootstrap replaces ShareNext
// ShareNext.defaultLayer, // kilocode_change - handled by KilocodeBootstrap
// kilocode_change start
KilocodeBootstrap.defaultLayer,
// ShareNext.defaultLayer,
// kilocode_change end
Snapshot.defaultLayer,
Vcs.defaultLayer,
]),
@@ -1,12 +1,16 @@
import { AllowEverythingPermission } from "@/kilocode/permission/allow-everything" // kilocode_change
import { Permission } from "@/permission"
import { PermissionID } from "@/permission/schema"
import { SessionID } from "@/session/schema" // kilocode_change
// kilocode_change start
import { SessionID } from "@/session/schema"
import { Effect, Schema } from "effect"
// kilocode_change end
import { HttpApiBuilder } from "effect/unstable/httpapi"
import { InstanceHttpApi } from "../api"
import { notFound } from "../errors" // kilocode_change
import { AllowEverythingBody, SaveAlwaysRulesBody } from "../groups/permission" // kilocode_change
// kilocode_change start
import { notFound } from "../errors"
import { AllowEverythingBody, SaveAlwaysRulesBody } from "../groups/permission"
// kilocode_change end
export const permissionHandlers = HttpApiBuilder.group(InstanceHttpApi, "permission", (handlers) =>
Effect.gen(function* () {
@@ -21,6 +25,7 @@ export const permissionHandlers = HttpApiBuilder.group(InstanceHttpApi, "permiss
payload: Permission.ReplyBody
}) {
const ok = yield* svc.reply({
// kilocode_change
requestID: ctx.params.requestID,
reply: ctx.payload.reply,
message: ctx.payload.message,
@@ -42,9 +47,7 @@ export const permissionHandlers = HttpApiBuilder.group(InstanceHttpApi, "permiss
if (!ok) return yield* notFound(`Permission request not found: ${ctx.params.requestID}`)
return true
})
// kilocode_change end
// kilocode_change start
const allowEverything = Effect.fn("PermissionHttpApi.allowEverything")(function* (ctx: {
payload: Schema.Schema.Type<typeof AllowEverythingBody>
}) {
@@ -54,12 +57,12 @@ export const permissionHandlers = HttpApiBuilder.group(InstanceHttpApi, "permiss
sessionID: ctx.payload.sessionID ? SessionID.make(ctx.payload.sessionID) : undefined,
})
})
// kilocode_change end
return handlers
.handle("list", list)
.handle("reply", reply)
.handle("saveAlwaysRules", saveAlwaysRules)
.handle("allowEverything", allowEverything) // kilocode_change
.handle("allowEverything", allowEverything)
// kilocode_change end
}),
)
@@ -37,7 +37,7 @@ import {
ShellPayload,
SummarizePayload,
UpdatePayload,
ViewedPayload,
ViewedPayload, // kilocode_change
} from "../groups/session"
import * as SessionError from "./session-errors"
@@ -265,8 +265,7 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
const instance = yield* InstanceState.context
const workspace = yield* InstanceState.workspaceID
const message = yield* promptSvc
// kilocode_change - cast to bridge schema-readonly→PromptInput-mutable; matches legacy Hono session.ts
.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput)
.prompt({ ...ctx.payload, sessionID: ctx.params.sessionID } as unknown as SessionPrompt.PromptInput) // kilocode_change
.pipe(
Effect.provideService(InstanceRef, instance),
Effect.provideService(WorkspaceRef, workspace),
@@ -1,6 +1,8 @@
import { OpenApi } from "effect/unstable/httpapi"
import { matchLegacyKiloOpenApi } from "@/kilocode/server/httpapi/public" // kilocode_change
import * as KiloServer from "@/kilocode/server/server" // kilocode_change
// kilocode_change start
import { matchLegacyKiloOpenApi } from "@/kilocode/server/httpapi/public"
import * as KiloServer from "@/kilocode/server/server"
// kilocode_change end
import { OpenCodeHttpApi } from "./api"
import { QueryBooleanOpenApi } from "./groups/query"
@@ -62,9 +64,11 @@ const QueryParameterSchemas: Record<string, OpenApiSchema> = {
"GET /experimental/session roots": QueryBooleanOpenApi,
"GET /experimental/session archived": QueryBooleanOpenApi,
"GET /find/file limit": { type: "integer", minimum: 1, maximum: 200 },
"GET /experimental/session worktrees": { type: "boolean" }, // kilocode_change
"GET /kilo/cloud-sessions cursor": { type: "string" }, // kilocode_change
"GET /kilo/cloud-sessions limit": { type: "number" }, // kilocode_change
// kilocode_change start
"GET /experimental/session worktrees": { type: "boolean" },
"GET /kilo/cloud-sessions cursor": { type: "string" },
"GET /kilo/cloud-sessions limit": { type: "number" },
// kilocode_change end
"GET /experimental/session cursor": { type: "number" },
"GET /experimental/session limit": { type: "number" },
"GET /session start": { type: "number" },
@@ -77,6 +81,7 @@ const QueryParameterSchemas: Record<string, OpenApiSchema> = {
"GET /api/session/{sessionID}/message limit": { type: "number" },
}
// kilocode_change start
const PathParameterSchemas: Record<string, OpenApiSchema> = {
sessionID: { type: "string", pattern: "^ses.*" },
messageID: { type: "string", pattern: "^msg.*" },
@@ -84,6 +89,7 @@ const PathParameterSchemas: Record<string, OpenApiSchema> = {
permissionID: { type: "string", pattern: "^per.*" },
ptyID: { type: "string", pattern: "^pty.*" },
}
// kilocode_change end
const LegacyComponentDescriptions: Record<string, string> = {
LogLevel: "Log level",
@@ -503,7 +509,7 @@ function flattenOptions(options: OpenApiSchema[] | undefined): OpenApiSchema[] |
function normalizeParameter(param: OpenApiParameter, route: string) {
if (!param.schema || typeof param.schema !== "object") return
if (param.in === "path") {
param.schema = pathParameterSchema(route, param.name) ?? stripOptionalNull(param.schema)
param.schema = pathParameterSchema(route, param.name) ?? stripOptionalNull(param.schema) // kilocode_change
return
}
if (param.in === "query") {
@@ -516,17 +522,19 @@ function normalizeParameter(param: OpenApiParameter, route: string) {
param.schema = stripOptionalNull(param.schema)
}
// kilocode_change start
function pathParameterSchema(route: string, name: string) {
if (name in PathParameterSchemas) return PathParameterSchemas[name]
if (name === "id" && route.startsWith("DELETE /experimental/workspace/")) return { type: "string", pattern: "^wrk.*" }
if (name === "id" && route.startsWith("POST /experimental/workspace/")) return { type: "string", pattern: "^wrk.*" }
if (name === "processID" && route.includes(" /background-process/")) return { type: "string", pattern: "^bgp.*" } // kilocode_change
if (name === "processID" && route.includes(" /background-process/")) return { type: "string", pattern: "^bgp.*" }
if (name === "requestID" && route.startsWith("POST /permission/")) return { type: "string", pattern: "^per.*" }
if (name === "requestID" && route.startsWith("POST /question/")) return { type: "string", pattern: "^que.*" }
// /network/* reuses QuestionID (prefix "que"), not a separate brand. // kilocode_change
if (name === "requestID" && route.startsWith("POST /network/")) return { type: "string", pattern: "^que.*" } // kilocode_change
// /network/* reuses QuestionID (prefix "que"), not a separate brand.
if (name === "requestID" && route.startsWith("POST /network/")) return { type: "string", pattern: "^que.*" }
return undefined
}
// kilocode_change end
export const PublicApi = OpenCodeHttpApi.annotateMerge(
OpenApi.annotations({
@@ -179,7 +179,7 @@ const uiRoute = HttpRouter.use((router) =>
export function createRoutes(corsOptions?: CorsOptions) {
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, docRoute, uiRoute).pipe(
Layer.provide(schemaErrorLayer),
Layer.provide(schemaErrorLayer), // kilocode_change
Layer.provide([
errorLayer,
compressionLayer,
+8 -11
View File
@@ -18,11 +18,13 @@ import { InstanceState } from "@/effect/instance-state"
import { isOverflow as overflow, usable } from "./overflow"
import { makeRuntime } from "@/effect/run-service"
import { serviceUse } from "@/effect/service-use"
import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue" // kilocode_change
import { KiloCompactionPayloadRecovery } from "@/kilocode/session/compaction-payload-recovery" // kilocode_change
import { KiloCompactionChunks } from "@/kilocode/session/compaction-chunks" // kilocode_change
import { SessionExport } from "@/kilocode/session-export" // kilocode_change
import { KiloSession } from "@/kilocode/session" // kilocode_change
// kilocode_change start
import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue"
import { KiloCompactionPayloadRecovery } from "@/kilocode/session/compaction-payload-recovery"
import { KiloCompactionChunks } from "@/kilocode/session/compaction-chunks"
import { SessionExport } from "@/kilocode/session-export"
import { KiloSession } from "@/kilocode/session"
// kilocode_change end
import { SyncEvent } from "@/sync"
import { SessionEvent } from "@/v2/session-event"
import { Flag } from "@opencode-ai/core/flag/flag"
@@ -478,9 +480,7 @@ export const layer: Layer.Layer<
updateMessage: session.updateMessage,
updatePart: session.updatePart,
})
// kilocode_change end
// kilocode_change start - fallback to chunked compaction when the first summary overflows
const fallback = KiloCompactionChunks.eligible({
result,
error: processor.message.error ?? processor.compactError?.(),
@@ -638,7 +638,6 @@ export const layer: Layer.Layer<
include: selected.tail_start_id,
})
}
// kilocode_change start - export self-contained compaction capture and prune stale tool output
const parent = KiloSession.resolveParent(input.sessionID)
const found = KiloSession.resolveRoot(input.sessionID)
const root = parent ? (found === input.sessionID ? parent : found) : input.sessionID
@@ -668,11 +667,10 @@ export const layer: Layer.Layer<
},
})
yield* prune({ sessionID: input.sessionID, reason: "post-compaction" })
// kilocode_change end
yield* bus.publish(Event.Compacted, { sessionID: input.sessionID })
}
return fallback
// kilocode_change end
return fallback // kilocode_change
})
const create = Effect.fn("SessionCompaction.create")(function* (input: {
@@ -735,7 +733,6 @@ export const defaultLayer = Layer.suspend(() =>
const { runPromise } = makeRuntime(Service, defaultLayer)
export async function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: Provider.Model }) {
// kilocode_change
return runPromise((svc) => svc.isOverflow(input))
}
+24 -16
View File
@@ -18,9 +18,11 @@ import { SessionStatus } from "./status"
import { SessionSummary } from "./summary"
import type { Provider } from "@/provider/provider"
import { Question } from "@/question"
import { KiloSessionProcessor, type ReviewTelemetry } from "@/kilocode/session/processor" // kilocode_change
import { Suggestion } from "@/kilocode/suggestion" // kilocode_change
import { NotFoundError } from "@/storage/storage" // kilocode_change
// kilocode_change start
import { KiloSessionProcessor, type ReviewTelemetry } from "@/kilocode/session/processor"
import { Suggestion } from "@/kilocode/suggestion"
import { NotFoundError } from "@/storage/storage"
// kilocode_change end
import { errorMessage } from "@/util/error"
import * as Log from "@opencode-ai/core/util/log"
import { isRecord } from "@/util/record"
@@ -60,8 +62,10 @@ type Input = {
assistantMessage: MessageV2.Assistant
sessionID: SessionID
model: Provider.Model
telemetry?: ReviewTelemetry // kilocode_change
snapshotInitialization?: "wait" // kilocode_change - managed clients wait silently for slow baseline creation
// kilocode_change start
telemetry?: ReviewTelemetry
snapshotInitialization?: "wait"
// kilocode_change end
}
export interface Interface {
@@ -84,8 +88,10 @@ interface ProcessorContext extends Input {
compactionError: ReturnType<typeof MessageV2.ContextOverflowError.prototype.toObject> | undefined // kilocode_change
currentText: MessageV2.TextPart | undefined
reasoningMap: Record<string, MessageV2.ReasoningPart>
stepStart: number // kilocode_change
step: { reasoning: boolean; text: boolean; tool: boolean } // kilocode_change
// kilocode_change start
stepStart: number
step: { reasoning: boolean; text: boolean; tool: boolean }
// kilocode_change end
}
type StreamEvent = Event
@@ -147,9 +153,11 @@ export const layer: Layer.Layer<
compactionError: undefined, // kilocode_change
currentText: undefined,
reasoningMap: {},
telemetry: input.telemetry, // kilocode_change
stepStart: 0, // kilocode_change
step: { reasoning: false, text: false, tool: false }, // kilocode_change
// kilocode_change start
telemetry: input.telemetry,
stepStart: 0,
step: { reasoning: false, text: false, tool: false },
// kilocode_change end
}
let aborted = false
const ac = new AbortController() // kilocode_change — abort controller for offline handler
@@ -384,8 +392,8 @@ export const layer: Layer.Layer<
if (ctx.assistantMessage.summary) {
throw new Error(`Tool call not allowed while generating summary: ${value.toolName}`)
}
ctx.step.tool = true // kilocode_change
// kilocode_change start — create tool part if tool-input-start was never emitted
// kilocode_change start
ctx.step.tool = true
if (!ctx.toolcalls[value.toolCallId]) {
log.warn("tool-call without prior tool-input-start", {
toolCallId: value.toolCallId,
@@ -549,9 +557,9 @@ export const layer: Layer.Layer<
throw value.error
case "start-step":
ctx.stepStart = performance.now() // kilocode_change
ctx.step = { reasoning: false, text: false, tool: false } // kilocode_change
// kilocode_change start - pass turn context for slow-snapshot UI/policy handling
// kilocode_change start
ctx.stepStart = performance.now()
ctx.step = { reasoning: false, text: false, tool: false }
if (!ctx.snapshot)
ctx.snapshot = yield* snapshot.track({
sessionID: ctx.sessionID,
@@ -946,7 +954,7 @@ export const layer: Layer.Layer<
if (ctx.needsCompaction) return "compact"
if (ctx.blocked || ctx.assistantMessage.error) return "stop"
return "continue" // kilocode_change - remove once compactError is no longer Kilo-specific
return "continue"
})
})
+21 -8
View File
@@ -61,7 +61,7 @@ import { InstanceState } from "@/effect/instance-state"
import { TaskTool, type TaskPromptOps } from "@/tool/task"
import { SessionRunState } from "./run-state"
import { EffectBridge } from "@/effect/bridge"
import { SyncEvent } from "@/sync"
import { SyncEvent } from "@/sync" // kilocode_change - preserve Kilo v2 event dual-write wiring
import { SessionEvent } from "@/v2/session-event"
import { Modelv2 } from "@/v2/model"
import { AgentAttachment, FileAttachment, Source } from "@/v2/session-prompt"
@@ -132,7 +132,7 @@ export const layer = Layer.effect(
const summary = yield* SessionSummary.Service
const sys = yield* SystemPrompt.Service
const llm = yield* LLM.Service
const sync = yield* SyncEvent.Service
const sync = yield* SyncEvent.Service // kilocode_change - preserve Kilo v2 event dual-write wiring
const runner = Effect.fn("SessionPrompt.runner")(function* () {
return yield* EffectBridge.make()
})
@@ -807,7 +807,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
throw error
}
const model = input.model ?? agent.model ?? (yield* currentModel(input.sessionID))
const model = input.model ?? agent.model ?? (yield* currentModel(input.sessionID)) // kilocode_change
const userMsg: MessageV2.User = {
id: input.messageID ?? MessageID.ascending(),
sessionID: input.sessionID,
@@ -858,6 +858,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
},
}
yield* sessions.updatePart(part)
// kilocode_change start - preserve Kilo v2 shell event dual-write
if (Flag.KILO_EXPERIMENTAL_EVENT_SYSTEM) {
yield* sync.run(SessionEvent.Shell.Started.Sync, {
sessionID: input.sessionID,
@@ -866,6 +867,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
command: input.command,
})
}
// kilocode_change end
return { msg, part, cwd: ctx.directory }
}).pipe(Effect.ensuring(markReady))
@@ -881,6 +883,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
output += "\n\n" + ["<metadata>", "User aborted the command", "</metadata>"].join("\n")
}
const completed = Date.now()
// kilocode_change start - preserve Kilo v2 shell event dual-write
if (Flag.KILO_EXPERIMENTAL_EVENT_SYSTEM) {
yield* sync.run(SessionEvent.Shell.Ended.Sync, {
sessionID: input.sessionID,
@@ -889,6 +892,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
output,
})
}
// kilocode_change end
if (!msg.time.completed) {
msg.time.completed = completed
yield* sessions.updateMessage(msg)
@@ -969,6 +973,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
return yield* Effect.failCause(exit.cause)
})
// kilocode_change start - preserve persisted per-session model selection
const currentModel = Effect.fnUntraced(function* (sessionID: SessionID) {
const current = Database.use((db) =>
db.select({ model: SessionTable.model }).from(SessionTable).where(eq(SessionTable.id, sessionID)).get(),
@@ -984,6 +989,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model
return yield* provider.defaultModel()
})
// kilocode_change end
const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) {
const agentName = input.agent || (yield* agents.defaultAgent())
@@ -1003,7 +1009,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
.where(eq(SessionTable.id, input.sessionID))
.get(),
)
const model = input.model ?? ag.model ?? (yield* currentModel(input.sessionID))
const model = input.model ?? ag.model ?? (yield* currentModel(input.sessionID)) // kilocode_change
const same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID
const full =
!input.variant && ag.variant && same
@@ -1325,6 +1331,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
return [{ ...part, messageID: info.id, sessionID: input.sessionID }]
})
// kilocode_change start - resolve and persist the exact transformed Kilo prompt parts
const resolvedParts = yield* Effect.forEach(input.parts, resolvePart, { concurrency: "unbounded" }).pipe(
Effect.map((x) => x.flat().map(assign)),
)
@@ -1342,6 +1349,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
)
const parts = resolvedParts
// kilocode_change end
const parsed = MessageV2.Info.zod.safeParse(info)
if (!parsed.success) {
@@ -1414,6 +1422,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
synthetic: [] as string[],
},
)
// kilocode_change start - preserve Kilo v2 prompt event dual-write
// TODO(v2): Temporary dual-write while migrating session messages to v2 events.
if (Flag.KILO_EXPERIMENTAL_EVENT_SYSTEM) {
yield* sync.run(SessionEvent.Prompted.Sync, {
@@ -1436,6 +1445,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
})
}
}
// kilocode_change end
return { info, parts }
}, Effect.scoped)
@@ -1444,9 +1454,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the
function* (input: PromptInput) {
const session = yield* sessions.get(input.sessionID).pipe(Effect.orDie)
yield* revert.cleanup(session)
// kilocode_change start - persist queued prompts immediately while serializing each follow-up loop
// kilocode_change start - recover interrupted Kilo turns before accepting a follow-up
yield* KiloSessionPrompt.recoverDanglingAssistant({ sessionID: input.sessionID, status, sessions })
yield* KiloSessionPrompt.recoverProviderFinishError({ sessionID: input.sessionID, status, sessions })
// kilocode_change end
const message = yield* createUserMessage(input)
yield* sessions.touch(input.sessionID)
@@ -1966,7 +1977,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
if (cmdAgent?.model) return cmdAgent.model
}
if (input.model) return Provider.parseModel(input.model)
return yield* currentModel(input.sessionID)
return yield* currentModel(input.sessionID) // kilocode_change
})
yield* getModel(taskModel.providerID, taskModel.modelID, input.sessionID)
@@ -2000,7 +2011,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
const userModel = isSubtask
? input.model
? Provider.parseModel(input.model)
: yield* currentModel(input.sessionID)
: yield* currentModel(input.sessionID) // kilocode_change
: taskModel
yield* plugin.trigger(
@@ -2066,7 +2077,7 @@ export const defaultLayer = Layer.suspend(() =>
LLM.defaultLayer,
Bus.layer,
CrossSpawnSpawner.defaultLayer,
SyncEvent.defaultLayer,
SyncEvent.defaultLayer, // kilocode_change - provide Kilo v2 event dual-write service
),
),
),
@@ -2106,6 +2117,7 @@ export const PromptInput = Schema.Struct({
]).annotate({ discriminator: "type" }),
),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
// kilocode_change start - retain precise prompt input types for Kilo callers
// `z.discriminatedUnion` erases the discriminated members' shapes back to
// `{}` when walked from the generic `z.ZodType` input. Restore the precise
// `parts` type from the exported Schema input types so callers see a proper
@@ -2119,6 +2131,7 @@ export type PromptInput = Omit<Schema.Schema.Type<typeof PromptInput>, "parts" |
parts: PartInputUnion[]
editorContext?: MessageV2.EditorContext
}
// kilocode_change end
export class LoopInput extends Schema.Class<LoopInput>("SessionPrompt.LoopInput")({
sessionID: SessionID,
+6 -2
View File
@@ -58,8 +58,10 @@ export const GlobTool = Tool.define(
},
})
const base = absolute?.dir ?? params.path ?? ins.directory // kilocode_change
// kilocode_change start
const base = absolute?.dir ?? params.path ?? ins.directory
const search = path.isAbsolute(base) ? base : path.resolve(ins.directory, base)
// kilocode_change end
yield* reference.ensure(search)
const info = yield* fs.stat(search).pipe(Effect.catch(() => Effect.succeed(undefined)))
if (info?.type === "File") {
@@ -72,8 +74,9 @@ export const GlobTool = Tool.define(
const limit = 100
let truncated = false
// kilocode_change start
const files = yield* rg
.files({ cwd: search, glob: [absolute?.pattern ?? params.pattern], signal: ctx.abort }) // kilocode_change
.files({ cwd: search, glob: [absolute?.pattern ?? params.pattern], signal: ctx.abort })
.pipe(
Stream.mapEffect((file) =>
Effect.gen(function* () {
@@ -91,6 +94,7 @@ export const GlobTool = Tool.define(
Stream.runCollect,
Effect.map((chunk) => [...chunk]),
)
// kilocode_change end
if (files.length > limit) {
truncated = true
+2 -2
View File
@@ -364,8 +364,8 @@ export const ReadTool = Tool.define(
// routed through TextStream.withFallback so non-UTF-8 files are decoded via
// iconv. The body otherwise matches upstream.
export async function lines(filepath: string, opts: { limit: number; offset: number }) {
const extracted = await Extract.open(filepath) // kilocode_change - extract supported document contents before paging
if (extracted) return readLines(extracted, opts) // kilocode_change
const extracted = await Extract.open(filepath)
if (extracted) return readLines(extracted, opts)
return TextStream.withFallback(filepath, (stream) => readLines(stream, opts))
}
+37 -32
View File
@@ -1,8 +1,10 @@
import { PlanExitTool } from "./plan"
import { Session } from "@/session/session"
import { QuestionTool } from "./question"
import { SuggestTool } from "../kilocode/suggestion/tool" // kilocode_change
import { Command } from "@/command" // kilocode_change
// kilocode_change start
import { SuggestTool } from "../kilocode/suggestion/tool"
import { Command } from "@/command"
// kilocode_change end
import { ShellTool } from "./shell"
import { EditTool } from "./edit"
import { GlobTool } from "./glob"
@@ -106,8 +108,10 @@ export const layer: Layer.Layer<
| Ripgrep.Service
| Format.Service
| Truncate.Service
| Command.Service // kilocode_change
| SessionStatus.Service // kilocode_change
// kilocode_change start
| Command.Service
| SessionStatus.Service
// kilocode_change end
> = Layer.effect(
Service,
Effect.gen(function* () {
@@ -247,6 +251,7 @@ export const layer: Layer.Layer<
return {
custom,
// kilocode_change start
builtin: KiloToolRegistry.describe(
[
tool.invalid,
@@ -264,15 +269,14 @@ export const layer: Layer.Layer<
...(Flag.KILO_EXPERIMENTAL_SCOUT ? [tool.code, tool.repo_clone, tool.repo_overview] : []),
tool.skill,
tool.patch,
// kilocode_change start
tool.plan,
...(["cli", "vscode"].includes(Flag.KILO_CLIENT) ? [tool.suggest] : []),
...KiloToolRegistry.extra(kilo, cfg),
// kilocode_change end
...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []),
],
kilo,
), // kilocode_change
),
// kilocode_change end
task: tool.task,
read: tool.read,
}
@@ -375,31 +379,32 @@ export const layer: Layer.Layer<
}),
)
export const defaultLayer = Layer.suspend(() =>
layer
.pipe(
Layer.provide(Config.defaultLayer),
Layer.provide(Plugin.defaultLayer),
Layer.provide(Question.defaultLayer),
Layer.provide(Todo.defaultLayer),
Layer.provide(Skill.defaultLayer),
Layer.provide(Agent.defaultLayer),
Layer.provide(Session.defaultLayer),
Layer.provide(Provider.defaultLayer),
Layer.provide(Git.defaultLayer),
Layer.provide(Reference.defaultLayer),
Layer.provide(LSP.defaultLayer),
Layer.provide(Instruction.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Bus.layer),
Layer.provide(FetchHttpClient.layer),
Layer.provide(Format.defaultLayer),
Layer.provide(CrossSpawnSpawner.defaultLayer),
Layer.provide(Ripgrep.defaultLayer),
Layer.provide(Truncate.defaultLayer),
)
// kilocode_change start
.pipe(Layer.provide(Command.defaultLayer), Layer.provide(SessionStatus.defaultLayer)),
export const defaultLayer = Layer.suspend(
() =>
layer
.pipe(
Layer.provide(Config.defaultLayer),
Layer.provide(Plugin.defaultLayer),
Layer.provide(Question.defaultLayer),
Layer.provide(Todo.defaultLayer),
Layer.provide(Skill.defaultLayer),
Layer.provide(Agent.defaultLayer),
Layer.provide(Session.defaultLayer),
Layer.provide(Provider.defaultLayer),
Layer.provide(Git.defaultLayer),
Layer.provide(Reference.defaultLayer),
Layer.provide(LSP.defaultLayer),
Layer.provide(Instruction.defaultLayer),
Layer.provide(AppFileSystem.defaultLayer),
Layer.provide(Bus.layer),
Layer.provide(FetchHttpClient.layer),
Layer.provide(Format.defaultLayer),
Layer.provide(CrossSpawnSpawner.defaultLayer),
Layer.provide(Ripgrep.defaultLayer),
Layer.provide(Truncate.defaultLayer),
)
// kilocode_change start - provide Kilo-owned registry dependencies
.pipe(Layer.provide(Command.defaultLayer), Layer.provide(SessionStatus.defaultLayer)),
// kilocode_change end
)
export * as ToolRegistry from "./registry"
@@ -32,7 +32,7 @@ const insideEagerRefreshWindow = Duration.toMillis(Duration.minutes(1))
const outsideEagerRefreshWindow = Duration.toMillis(Duration.minutes(10))
const live = (client: HttpClient.HttpClient) =>
Layer.fresh(Account.layer).pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, client)))
Layer.fresh(Account.layer).pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, client))) // kilocode_change
const json = (req: Parameters<typeof HttpClientResponse.fromWeb>[0], body: unknown, status = 200) =>
HttpClientResponse.fromWeb(
+7 -10
View File
@@ -42,9 +42,11 @@ test("returns default native agents when no config", async () => {
const names = agents.map((a) => a.name)
expect(names).toContain("code") // kilocode_change
expect(names).toContain("plan")
expect(names).toContain("debug") // kilocode_change
expect(names).toContain("orchestrator") // kilocode_change
expect(names).toContain("ask") // kilocode_change
// kilocode_change start
expect(names).toContain("debug")
expect(names).toContain("orchestrator")
expect(names).toContain("ask")
// kilocode_change end
expect(names).toContain("general")
expect(names).toContain("explore")
expect(names).not.toContain("scout")
@@ -67,15 +69,13 @@ test("code agent has correct default properties", async () => {
expect(code?.mode).toBe("primary")
expect(code?.native).toBe(true)
expect(evalPerm(code, "edit")).toBe("allow")
expect(evalPerm(code, "bash")).toBe("ask") // kilocode_change - safe-bash default is ask
expect(evalPerm(code, "bash")).toBe("ask")
expect(evalPerm(code, "repo_clone")).toBe("deny")
expect(evalPerm(code, "repo_overview")).toBe("deny")
},
})
})
// kilocode_change end
// kilocode_change start - ask agent tests
test("ask agent has correct default properties", async () => {
await using tmp = await tmpdir()
await WithInstance.provide({
@@ -133,9 +133,7 @@ test("ask agent denies edit/write/bash even when user config adds a specific edi
},
})
})
// kilocode_change end
// kilocode_change start
test("plan agent denies edits except .kilo/plans/* and .opencode/plans/*", async () => {
await using tmp = await tmpdir()
await WithInstance.provide({
@@ -539,6 +537,7 @@ test("unknown agent properties are placed into options", async () => {
config: {
agent: {
code: {
// kilocode_change
random_property: "hello",
another_random: 123,
},
@@ -958,7 +957,6 @@ test("defaultAgent returns plan when code is disabled and default_agent not set"
directory: tmp.path,
fn: async () => {
const agent = await load(tmp.path, (svc) => svc.defaultAgent())
// kilocode_change - code is disabled, so it should return plan (next primary agent)
expect(agent).toBe("plan")
},
})
@@ -981,7 +979,6 @@ test("defaultAgent throws when all primary agents are disabled", async () => {
await WithInstance.provide({
directory: tmp.path,
fn: async () => {
// kilocode_change - all primary agents are disabled
await expect(load(tmp.path, (svc) => svc.defaultAgent())).rejects.toThrow("no primary visible agent found")
},
})
@@ -1,4 +1,3 @@
// kilocode_change - new file
//
// Ensure the edit tool always includes `filediff` in its
// permission-ask metadata. Without `filediff`, the VS Code extension's
@@ -174,7 +174,7 @@ function makeHttp() {
Layer.provideMerge(proc),
Layer.provideMerge(registry),
Layer.provideMerge(trunc),
Layer.provideMerge(question), // kilocode_change - SessionPrompt now dismisses questions via its service dependency
Layer.provideMerge(question),
Layer.provide(Instruction.defaultLayer),
Layer.provide(SystemPrompt.defaultLayer),
Layer.provideMerge(deps),
@@ -1,4 +1,3 @@
// kilocode_change - new file
import { describe, expect, test } from "bun:test"
import { MessageV2 } from "../../src/session/message-v2"
@@ -167,7 +167,7 @@ function makeHttp() {
Layer.provideMerge(proc),
Layer.provideMerge(registry),
Layer.provideMerge(trunc),
Layer.provideMerge(question), // kilocode_change - SessionPrompt now dismisses questions via its service dependency
Layer.provideMerge(question),
Layer.provide(Instruction.defaultLayer),
Layer.provide(SystemPrompt.defaultLayer),
Layer.provideMerge(deps),
+3 -1
View File
@@ -4,7 +4,7 @@ import type * as Scope from "effect/Scope"
import * as TestClock from "effect/testing/TestClock"
import * as TestConsole from "effect/testing/TestConsole"
import type { Config } from "@/config/config"
import { Reference } from "@/reference/reference"
import { Reference } from "@/reference/reference" // kilocode_change
import { TestInstance, withTmpdirInstance } from "../fixture/fixture"
type Body<A, E, R> = Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>)
@@ -108,7 +108,9 @@ const liveEnv = TestConsole.layer
export const it = make(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))
}
// kilocode_change end
@@ -1,11 +1,15 @@
import { afterAll, afterEach, test, expect } from "bun:test" // kilocode_change
import fs from "fs/promises" // kilocode_change
// kilocode_change start
import { afterAll, afterEach, test, expect } from "bun:test"
import fs from "fs/promises"
// kilocode_change end
import os from "os"
import path from "path" // kilocode_change
import { Cause, Effect, Exit, Fiber, Layer } from "effect"
import { Bus } from "../../src/bus"
import { Config } from "../../src/config/config" // kilocode_change
import { Global } from "@opencode-ai/core/global" // kilocode_change
// kilocode_change start
import { Config } from "../../src/config/config"
import { Global } from "@opencode-ai/core/global"
// kilocode_change end
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { Permission } from "../../src/permission"
import { PermissionID } from "../../src/permission/schema"
@@ -1097,7 +1101,6 @@ it.live("permission requests stay isolated by directory", () =>
const onePending = yield* waitForPending(1).pipe(runOne)
const twoPending = yield* waitForPending(1).pipe(runTwo)
// kilocode_change start
expect(onePending).toHaveLength(1)
expect(twoPending).toHaveLength(1)
expect(onePending[0].id).toBe(PermissionID.make("per_dir_a"))
@@ -1108,7 +1111,6 @@ it.live("permission requests stay isolated by directory", () =>
yield* Fiber.await(a)
yield* Fiber.await(b)
// kilocode_change end
}),
)
@@ -89,6 +89,7 @@ const queryParamPatterns = [
] satisfies Array<{ method: Method; path: string; name: string; pattern: string }>
const pathParamPatterns = [
// kilocode_change start
{ method: "get", path: SessionPaths.get, name: "sessionID", pattern: "^ses.*" },
{ method: "get", path: SessionPaths.message, name: "messageID", pattern: "^msg.*" },
{ method: "patch", path: SessionPaths.updatePart, name: "partID", pattern: "^prt.*" },
@@ -97,6 +98,7 @@ const pathParamPatterns = [
{ method: "post", path: "/question/:requestID/reply", name: "requestID", pattern: "^que.*" },
{ method: "put", path: PtyPaths.update, name: "ptyID", pattern: "^pty.*" },
{ method: "delete", path: WorkspacePaths.remove, name: "id", pattern: "^wrk.*" },
// kilocode_change end
] satisfies Array<{ method: Method; path: string; name: string; pattern: string }>
function app() {
@@ -199,7 +199,7 @@ describe("HttpApi UI fallback", () => {
test("requires server password for the web UI", async () => {
Flag.KILO_DISABLE_EMBEDDED_WEB_UI = true
const response = await uiApp({ password: "secret", username: "kilo" }).request("/")
const response = await uiApp({ password: "secret", username: "kilo" }).request("/") // kilocode_change
expect(response.status).toBe(401)
expect(response.headers.get("www-authenticate")).toBe('Basic realm="Secure Area"')
@@ -210,22 +210,28 @@ describe("HttpApi UI fallback", () => {
const response = await uiApp({
password: "secret",
// kilocode_change start
username: "kilo",
}).request(`/?auth_token=${btoa("kilo:secret")}`)
expect(response.status).toBe(404)
expect(await response.json()).toEqual({ error: "Not Found" })
// kilocode_change end
})
test("accepts basic auth for the web UI", async () => {
Flag.KILO_DISABLE_EMBEDDED_WEB_UI = true
// kilocode_change start
const response = await uiApp({ password: "secret", username: "kilo" }).request("/", {
headers: { authorization: `Basic ${btoa("kilo:secret")}` },
// kilocode_change end
})
// kilocode_change start
expect(response.status).toBe(404)
expect(await response.json()).toEqual({ error: "Not Found" })
// kilocode_change end
})
// Regression for #25698 (Ope): the browser fetches the PWA manifest and
@@ -234,20 +240,22 @@ describe("HttpApi UI fallback", () => {
// server returning 401 breaks PWA install. These specific public assets
// should bypass auth.
test("allows public PWA assets through auth without proxying", async () => {
// kilocode_change
Flag.KILO_DISABLE_EMBEDDED_WEB_UI = true
for (const path of ["/site.webmanifest", "/web-app-manifest-192x192.png", "/web-app-manifest-512x512.png"]) {
const response = await uiApp({
password: "secret",
username: "kilo",
username: "kilo", // kilocode_change
client: httpClient(new Response("ok")),
}).request(path)
expect(response.status).toBe(404)
expect(response.status).toBe(404) // kilocode_change
}
})
test("allows web UI preflight without auth", async () => {
const response = await app({ password: "secret", username: "kilo" }).request("/", {
// kilocode_change
method: "OPTIONS",
headers: {
origin: "http://localhost:3000",
@@ -46,10 +46,12 @@ describe("getWorkspaceRouteSessionID", () => {
expect(getWorkspaceRouteSessionID(url)).toBeNull()
})
// kilocode_change start
test("returns null for Kilo's /session/viewed route", () => {
const url = new URL("http://localhost/session/viewed")
expect(getWorkspaceRouteSessionID(url)).toBeNull()
}) // kilocode_change
})
// kilocode_change end
test("returns null for non-session paths", () => {
const url = new URL("http://localhost/config")
+8 -8
View File
@@ -324,7 +324,7 @@ describe("session.llm.stream", () => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://app.kilo.ai/config.json",
$schema: "https://app.kilo.ai/config.json", // kilocode_change
enabled_providers: [providerID],
provider: {
[providerID]: {
@@ -412,7 +412,7 @@ describe("session.llm.stream", () => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
$schema: "https://opencode.ai/config.json", // kilocode_change
enabled_providers: [providerID],
provider: {
[providerID]: {
@@ -502,7 +502,7 @@ describe("session.llm.stream", () => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
$schema: "https://opencode.ai/config.json", // kilocode_change
enabled_providers: [providerID],
provider: {
[providerID]: {
@@ -609,7 +609,7 @@ describe("session.llm.stream", () => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://app.kilo.ai/config.json",
$schema: "https://app.kilo.ai/config.json", // kilocode_change
enabled_providers: ["openai"],
provider: {
openai: {
@@ -725,7 +725,7 @@ describe("session.llm.stream", () => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
$schema: "https://opencode.ai/config.json", // kilocode_change
enabled_providers: ["openai"],
provider: {
openai: {
@@ -851,7 +851,7 @@ describe("session.llm.stream", () => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://app.kilo.ai/config.json",
$schema: "https://app.kilo.ai/config.json", // kilocode_change
enabled_providers: [providerID],
provider: {
[providerID]: {
@@ -962,7 +962,7 @@ describe("session.llm.stream", () => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
$schema: "https://opencode.ai/config.json", // kilocode_change
enabled_providers: ["anthropic"],
provider: {
anthropic: {
@@ -1210,7 +1210,7 @@ describe("session.llm.stream", () => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://app.kilo.ai/config.json",
$schema: "https://app.kilo.ai/config.json", // kilocode_change
enabled_providers: [providerID],
provider: {
[providerID]: {
@@ -1,7 +1,9 @@
import { NodeFileSystem } from "@effect/platform-node"
import { FetchHttpClient } from "effect/unstable/http"
import { afterEach, expect, mock, spyOn } from "bun:test" // kilocode_change - spy on review telemetry
import { Telemetry } from "@kilocode/kilo-telemetry" // kilocode_change - assert review command telemetry
// kilocode_change start
import { afterEach, expect, mock, spyOn } from "bun:test"
import { Telemetry } from "@kilocode/kilo-telemetry"
// kilocode_change end
import { Cause, Effect, Exit, Fiber, Layer } from "effect"
import path from "path"
import { fileURLToPath } from "url"
@@ -595,8 +597,8 @@ it.live("loop continues when finish is tool-calls", () =>
),
)
// kilocode_change - skipped: tracked in #9958
it.live.skip("glob tool keeps instance context during prompt runs", () =>
// kilocode_change
provideTmpdirServer(
({ dir, llm }) =>
Effect.gen(function* () {
@@ -801,7 +803,7 @@ it.live(
}),
{ git: true, config: providerCfg },
),
10_000, // kilocode_change
10_000,
)
// kilocode_change start - child task failures stay tool errors so the parent can recover
@@ -913,7 +915,7 @@ it.live(
)
unix(
// kilocode_change - skip flaky cancel test on Windows CI
// kilocode_change
"cancel records MessageAbortedError on interrupted process",
() =>
provideTmpdirServer(
@@ -1153,7 +1155,7 @@ it.live(
const a = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild)
yield* llm.wait(1)
const b = yield* prompt.loop({ sessionID: chat.id }).pipe(Effect.forkChild)
yield* Effect.sleep(50) // kilocode_change - let b attach to a's done deferred before gate resolves
yield* Effect.sleep(50)
gate.resolve()
const [ea, eb] = yield* Effect.all([Fiber.await(a), Fiber.await(b)])
@@ -65,8 +65,7 @@ describe("Session.Info", () => {
additions: 10,
deletions: 5,
files: 2,
// kilocode_change - summary.diffs uses SummaryFileDiff (patch omitted) in kilo
diffs: [{ additions: 1, deletions: 0, file: "a.ts" }],
diffs: [{ additions: 1, deletions: 0, file: "a.ts" }], // kilocode_change
},
share: { url: "https://share.example.com/s/1" },
title: "Full session",
@@ -260,9 +259,9 @@ describe("SessionStatus.Info", () => {
reason: "free_tier_limit",
provider: "opencode",
title: "Free limit reached",
message: "Subscribe to OpenCode Go.",
message: "Subscribe to OpenCode Go.", // kilocode_change
label: "subscribe",
link: "https://opencode.ai/go",
link: "https://opencode.ai/go", // kilocode_change
},
next: 500,
}
+2
View File
@@ -1,7 +1,9 @@
import { describe, expect } from "bun:test"
import path from "path"
// kilocode_change start
import fs from "fs/promises"
import os from "os"
// kilocode_change end
import { Cause, Effect, Exit, Layer } from "effect"
import { GlobTool } from "../../src/tool/glob"
import { SessionID, MessageID } from "../../src/session/schema"
+2 -6
View File
@@ -36,6 +36,7 @@ const configLayer = TestConfig.layer({
})
const registryBase = ToolRegistry.layer.pipe(
// kilocode_change
Layer.provide(configLayer),
Layer.provide(Plugin.defaultLayer),
Layer.provide(Question.defaultLayer),
@@ -58,10 +59,7 @@ const registryBase = ToolRegistry.layer.pipe(
)
// kilocode_change start
const registryLayer = registryBase.pipe(
Layer.provide(Command.defaultLayer),
Layer.provide(SessionStatus.defaultLayer),
)
const registryLayer = registryBase.pipe(Layer.provide(Command.defaultLayer), Layer.provide(SessionStatus.defaultLayer))
// kilocode_change end
const it = testEffect(Layer.mergeAll(registryLayer, node))
@@ -95,9 +93,7 @@ describe("tool.registry", () => {
}
}),
)
// kilocode_change end
// kilocode_change start
it.live("suggest is registered for cli and vscode only", () =>
Effect.gen(function* () {
const original = process.env["KILO_CLIENT"]
+2 -2
View File
@@ -30,11 +30,11 @@ const node = CrossSpawnSpawner.defaultLayer
const it = testEffect(Layer.mergeAll(ToolRegistry.defaultLayer, node))
// kilocode_change - skip on windows: address windows ci failures #9496
const unix = process.platform !== "win32" ? it.live : it.live.skip
const unix = process.platform !== "win32" ? it.live : it.live.skip // kilocode_change
describe("tool.skill", () => {
unix("execute returns skill content block with files", () =>
// kilocode_change
provideTmpdirInstance(
(dir) =>
Effect.gen(function* () {