From 5706abce7693eaff417512b57a6ebee96d58525a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 8 Jun 2026 12:45:47 +0200 Subject: [PATCH] chore(cli): normalize upstream annotations --- packages/opencode/src/agent/agent.ts | 12 ++-- packages/opencode/src/cli/cmd/tui/app.tsx | 24 +++---- .../cli/cmd/tui/component/prompt/index.tsx | 16 +++-- .../src/cli/cmd/tui/routes/session/index.tsx | 46 +++++-------- .../cli/cmd/tui/routes/session/permission.tsx | 21 +++--- packages/opencode/src/config/config.ts | 52 +++++++------- .../opencode/src/kilocode/session/index.ts | 1 - .../opencode/src/kilocode/suggestion/index.ts | 1 - packages/opencode/src/permission/index.ts | 58 ++++++++-------- packages/opencode/src/project/bootstrap.ts | 18 +++-- .../instance/httpapi/handlers/permission.ts | 17 +++-- .../instance/httpapi/handlers/session.ts | 5 +- .../server/routes/instance/httpapi/public.ts | 26 ++++--- .../server/routes/instance/httpapi/server.ts | 2 +- packages/opencode/src/session/compaction.ts | 19 +++-- packages/opencode/src/session/processor.ts | 40 ++++++----- packages/opencode/src/session/prompt.ts | 29 +++++--- packages/opencode/src/tool/glob.ts | 8 ++- packages/opencode/src/tool/read.ts | 4 +- packages/opencode/src/tool/registry.ts | 69 ++++++++++--------- .../opencode/test/account/service.test.ts | 2 +- packages/opencode/test/agent/agent.test.ts | 17 ++--- .../kilocode/edit-permission-filediff.test.ts | 1 - .../kilocode/session-compaction-cap.test.ts | 2 +- .../kilocode/session-message-metadata.test.ts | 1 - .../session-prompt-compaction-safety.test.ts | 2 +- packages/opencode/test/lib/effect.ts | 4 +- .../opencode/test/permission/next.test.ts | 14 ++-- .../server/httpapi-query-schema-drift.test.ts | 2 + .../opencode/test/server/httpapi-ui.test.ts | 14 +++- .../test/server/workspace-routing.test.ts | 4 +- packages/opencode/test/session/llm.test.ts | 16 ++--- packages/opencode/test/session/prompt.test.ts | 14 ++-- .../test/session/schema-decoding.test.ts | 7 +- packages/opencode/test/tool/glob.test.ts | 2 + packages/opencode/test/tool/registry.test.ts | 8 +-- packages/opencode/test/tool/skill.test.ts | 4 +- 37 files changed, 315 insertions(+), 267 deletions(-) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index cf0456d5994..60c37f80c5c 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -101,7 +101,7 @@ export const layer = Layer.effect( } satisfies Record 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`) diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 213cbcf0783..3a003acfd8f 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -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 ( ( )} - // kilocode_change end > @@ -336,8 +335,10 @@ function App(props: { onSnapshot?: () => Promise }) { 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 }) { 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() } diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 5047b0443ef..cbbf090c85b 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -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) { 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() { )} - {/* kilocode_change end */} - {/* kilocode_change start */} {(request) => } @@ -1415,13 +1411,9 @@ export function Session() { - {/* kilocode_change end */} - {/* kilocode_change start */} - {/* kilocode_change end */} - {/* kilocode_change start */} - {/* kilocode_change */} + {/* kilocode_change start */} + {/* kilocode_change end */} - {/* kilocode_change */} + {/* kilocode_change start */} This will allow the following patterns permanently + {/* kilocode_change end */} {(pattern) => ( @@ -428,13 +432,13 @@ export function PermissionPrompt(props: { request: PermissionRequest }) { {current.title} - {/* // kilocode_change start - explain config file edits always require approval */} + {/* kilocode_change start - explain config file edits always require approval */} Config file edits always require approval - {/* // kilocode_change end */} + {/* kilocode_change end */} ) @@ -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: ( Reject permission - {/* kilocode_change */} Tell Kilo what to do differently diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index a486719cfe4..b3b00ad834f 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -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 readonly getConsoleState: () => Effect.Effect readonly update: (config: Info) => Effect.Effect + // 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 readonly directories: () => Effect.Effect readonly waitForDependencies: () => Effect.Effect @@ -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) : {} 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) diff --git a/packages/opencode/src/kilocode/session/index.ts b/packages/opencode/src/kilocode/session/index.ts index 9d719c5babd..9cd90e562ac 100644 --- a/packages/opencode/src/kilocode/session/index.ts +++ b/packages/opencode/src/kilocode/session/index.ts @@ -1,4 +1,3 @@ -// kilocode_change - new file import { remapChildren as _remapChildren } from "./fork" import z from "zod" import { Cause, Effect, Schema } from "effect" diff --git a/packages/opencode/src/kilocode/suggestion/index.ts b/packages/opencode/src/kilocode/suggestion/index.ts index be2c53a9a3e..19e82469dc3 100644 --- a/packages/opencode/src/kilocode/suggestion/index.ts +++ b/packages/opencode/src/kilocode/suggestion/index.ts @@ -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, diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 32e130795a4..acb016ba71f 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -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 readonly reply: (input: ReplyInput) => Effect.Effect // kilocode_change readonly list: () => Effect.Effect> - readonly saveAlwaysRules: (input: z.infer) => Effect.Effect // kilocode_change - readonly allowEverything: (input: z.infer) => Effect.Effect // kilocode_change - readonly pending: (id: string) => Effect.Effect // kilocode_change + // kilocode_change start + readonly saveAlwaysRules: (input: z.infer) => Effect.Effect + readonly allowEverything: (input: z.infer) => Effect.Effect + readonly pending: (id: string) => Effect.Effect + // 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 } @@ -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, 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, ) { 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 }), ) diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts index add44e27050..3fed2dd6073 100644 --- a/packages/opencode/src/project/bootstrap.ts +++ b/packages/opencode/src/project/bootstrap.ts @@ -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 = 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, ]), diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/permission.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/permission.ts index cbeb4d5548e..b1746541762 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/permission.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/permission.ts @@ -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 }) { @@ -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 }), ) diff --git a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts index 80a12301268..f3a99545588 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts @@ -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), diff --git a/packages/opencode/src/server/routes/instance/httpapi/public.ts b/packages/opencode/src/server/routes/instance/httpapi/public.ts index f2475febcf2..c8e5e1cd8be 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/public.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/public.ts @@ -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 = { "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 = { "GET /api/session/{sessionID}/message limit": { type: "number" }, } +// kilocode_change start const PathParameterSchemas: Record = { sessionID: { type: "string", pattern: "^ses.*" }, messageID: { type: "string", pattern: "^msg.*" }, @@ -84,6 +89,7 @@ const PathParameterSchemas: Record = { permissionID: { type: "string", pattern: "^per.*" }, ptyID: { type: "string", pattern: "^pty.*" }, } +// kilocode_change end const LegacyComponentDescriptions: Record = { 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({ diff --git a/packages/opencode/src/server/routes/instance/httpapi/server.ts b/packages/opencode/src/server/routes/instance/httpapi/server.ts index 4c59432b443..4fc985ed354 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/server.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/server.ts @@ -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, diff --git a/packages/opencode/src/session/compaction.ts b/packages/opencode/src/session/compaction.ts index a7d109363d4..413297315d9 100644 --- a/packages/opencode/src/session/compaction.ts +++ b/packages/opencode/src/session/compaction.ts @@ -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)) } diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index d2803fba51c..041559b1845 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -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 | undefined // kilocode_change currentText: MessageV2.TextPart | undefined reasoningMap: Record - 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" }) }) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 7feb26ebf2e..4e1160acdcf 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -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" + ["", "User aborted the command", ""].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, "parts" | parts: PartInputUnion[] editorContext?: MessageV2.EditorContext } +// kilocode_change end export class LoopInput extends Schema.Class("SessionPrompt.LoopInput")({ sessionID: SessionID, diff --git a/packages/opencode/src/tool/glob.ts b/packages/opencode/src/tool/glob.ts index f5e27281101..9065ecc23c4 100644 --- a/packages/opencode/src/tool/glob.ts +++ b/packages/opencode/src/tool/glob.ts @@ -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 diff --git a/packages/opencode/src/tool/read.ts b/packages/opencode/src/tool/read.ts index ac170a30ae2..b2e9d54d95b 100644 --- a/packages/opencode/src/tool/read.ts +++ b/packages/opencode/src/tool/read.ts @@ -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)) } diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index ec9923ab38c..ce81b706efc 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -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" diff --git a/packages/opencode/test/account/service.test.ts b/packages/opencode/test/account/service.test.ts index 20474edc486..bfd91687233 100644 --- a/packages/opencode/test/account/service.test.ts +++ b/packages/opencode/test/account/service.test.ts @@ -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[0], body: unknown, status = 200) => HttpClientResponse.fromWeb( diff --git a/packages/opencode/test/agent/agent.test.ts b/packages/opencode/test/agent/agent.test.ts index c45d6bcf95b..f14db0ad0e3 100644 --- a/packages/opencode/test/agent/agent.test.ts +++ b/packages/opencode/test/agent/agent.test.ts @@ -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") }, }) diff --git a/packages/opencode/test/kilocode/edit-permission-filediff.test.ts b/packages/opencode/test/kilocode/edit-permission-filediff.test.ts index 563857281ad..598365f03da 100644 --- a/packages/opencode/test/kilocode/edit-permission-filediff.test.ts +++ b/packages/opencode/test/kilocode/edit-permission-filediff.test.ts @@ -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 diff --git a/packages/opencode/test/kilocode/session-compaction-cap.test.ts b/packages/opencode/test/kilocode/session-compaction-cap.test.ts index 94e37538e54..3aa1e7e5a7b 100644 --- a/packages/opencode/test/kilocode/session-compaction-cap.test.ts +++ b/packages/opencode/test/kilocode/session-compaction-cap.test.ts @@ -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), diff --git a/packages/opencode/test/kilocode/session-message-metadata.test.ts b/packages/opencode/test/kilocode/session-message-metadata.test.ts index 35c78babd42..785ae99dc6f 100644 --- a/packages/opencode/test/kilocode/session-message-metadata.test.ts +++ b/packages/opencode/test/kilocode/session-message-metadata.test.ts @@ -1,4 +1,3 @@ -// kilocode_change - new file import { describe, expect, test } from "bun:test" import { MessageV2 } from "../../src/session/message-v2" diff --git a/packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts b/packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts index f94c030fdbc..7c4f5eab0c4 100644 --- a/packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts +++ b/packages/opencode/test/kilocode/session-prompt-compaction-safety.test.ts @@ -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), diff --git a/packages/opencode/test/lib/effect.ts b/packages/opencode/test/lib/effect.ts index d3a6620f11d..a4142b44a4b 100644 --- a/packages/opencode/test/lib/effect.ts +++ b/packages/opencode/test/lib/effect.ts @@ -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 = Effect.Effect | (() => Effect.Effect) @@ -108,7 +108,9 @@ const liveEnv = TestConsole.layer export const it = make(testEnv, liveEnv) +// kilocode_change start export const testEffect = (layer: Layer.Layer) => { const full = Layer.merge(layer, Reference.defaultLayer) return make(Layer.provideMerge(full, testEnv), Layer.provideMerge(full, liveEnv)) } +// kilocode_change end diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index 8748e84fe0e..479af6ba584 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -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 }), ) diff --git a/packages/opencode/test/server/httpapi-query-schema-drift.test.ts b/packages/opencode/test/server/httpapi-query-schema-drift.test.ts index 503878987ce..cb25ad1432d 100644 --- a/packages/opencode/test/server/httpapi-query-schema-drift.test.ts +++ b/packages/opencode/test/server/httpapi-query-schema-drift.test.ts @@ -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() { diff --git a/packages/opencode/test/server/httpapi-ui.test.ts b/packages/opencode/test/server/httpapi-ui.test.ts index 2213e04526c..6b4fc891aee 100644 --- a/packages/opencode/test/server/httpapi-ui.test.ts +++ b/packages/opencode/test/server/httpapi-ui.test.ts @@ -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", diff --git a/packages/opencode/test/server/workspace-routing.test.ts b/packages/opencode/test/server/workspace-routing.test.ts index 75935f86ade..d327850fb5e 100644 --- a/packages/opencode/test/server/workspace-routing.test.ts +++ b/packages/opencode/test/server/workspace-routing.test.ts @@ -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") diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 197f92f753b..ef8f1739bf1 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -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]: { diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index fa85baa8afb..c3663696acf 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -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)]) diff --git a/packages/opencode/test/session/schema-decoding.test.ts b/packages/opencode/test/session/schema-decoding.test.ts index c8f3f213663..191c507f081 100644 --- a/packages/opencode/test/session/schema-decoding.test.ts +++ b/packages/opencode/test/session/schema-decoding.test.ts @@ -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, } diff --git a/packages/opencode/test/tool/glob.test.ts b/packages/opencode/test/tool/glob.test.ts index 76ff3134955..b54dda91cec 100644 --- a/packages/opencode/test/tool/glob.test.ts +++ b/packages/opencode/test/tool/glob.test.ts @@ -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" diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 27912101617..78326c737dd 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -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"] diff --git a/packages/opencode/test/tool/skill.test.ts b/packages/opencode/test/tool/skill.test.ts index 7518526fdea..9954d4f36c5 100644 --- a/packages/opencode/test/tool/skill.test.ts +++ b/packages/opencode/test/tool/skill.test.ts @@ -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* () {