fix: align merged CI contracts

This commit is contained in:
marius-kilocode
2026-07-27 08:10:52 +02:00
parent bdf168b8a4
commit c8abc86fe7
9 changed files with 30 additions and 73 deletions
+3
View File
@@ -60,6 +60,9 @@ exclude = [
# Consistently times out in CI
'^https?://opncd\.ai',
'^https?://zod\.dev/v4/changelog',
# Moonshot sites consistently time out from GitHub Actions runners.
'^https?://(www\.)?moonshot\.cn/?$',
'^https?://platform\.moonshot\.cn/?$',
# Example punycode domain used in homograph attack documentation — does not exist
'^https?://xn--pitest',
# OpenAI docs return 404 to plain GET link checks but resolve in browsers
+1 -1
View File
@@ -1,2 +1,2 @@
[test]
preload = ["./tests/setup/vscode-mock.ts"]
preload = ["./tests/setup/vscode-mock.ts", "./tests/setup/worker-url.ts"]
@@ -0,0 +1,11 @@
import { plugin } from "bun"
plugin({
name: "worker-url",
setup(build) {
build.onLoad({ filter: /\?worker&url$/ }, () => ({
contents: "export default 'test-worker.js'",
loader: "js",
}))
},
})
@@ -19,6 +19,7 @@ import path from "node:path"
const MONOREPO_ROOT = path.resolve(import.meta.dir, "../../../..")
const KILO_UI_DIR = path.join(MONOREPO_ROOT, "packages/kilo-ui")
const WORKER_URL = path.join(MONOREPO_ROOT, "packages/kilo-vscode/tests/setup/worker-url.ts")
const BASIC_TOOL_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/basic-tool.tsx")
const DATA_CONTEXT_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/context/data.tsx")
const MESSAGE_PART_FILE = path.join(MONOREPO_ROOT, "packages/ui/src/components/message-part.tsx")
@@ -44,7 +45,7 @@ const TRANSCRIPT_PARTS_FILE = path.join(MONOREPO_ROOT, "packages/kilo-vscode/web
const CHAT_LAYOUT_FILE = path.join(MONOREPO_ROOT, "packages/kilo-vscode/webview-ui/src/styles/chat-layout.css")
function check(code: string): { ok: boolean; output: string } {
const result = Bun.spawnSync(["bun", "--conditions=browser", "-e", code], {
const result = Bun.spawnSync(["bun", "--preload", WORKER_URL, "--conditions=browser", "-e", code], {
cwd: KILO_UI_DIR,
stdout: "pipe",
stderr: "pipe",
@@ -1,62 +0,0 @@
import { describe, it, expect } from "bun:test"
import { readFileSync } from "node:fs"
import { join } from "node:path"
/**
* Regression guard for the markdown rAF-coalesced parse fix.
*
* PROBLEM:
* The `Markdown` component's render effect did `temp.innerHTML = content`
* + `morphdom(...)` on every update. During LLM token streaming, this
* fired 60–200 times per second, reparsing the entire accumulated HTML
* every time. CPU profile of a 7s streaming window showed 2,940 ParseHTML
* events (~619ms, ~46% of blocked main-thread time).
*
* FIX:
* Queue the latest content in a component-scoped variable and run the
* morphdom pass inside a requestAnimationFrame callback. Further updates
* before the frame fires simply overwrite the pending content — K rapid
* token updates collapse to 1 parse. The onCleanup handler cancels any
* queued frame so it doesn't touch the unmounted DOM.
*
* For the matching runtime assertion, see
* `tests/webview-reactivity/markdown-parse-rate.test.ts`.
*/
describe("Markdown rAF-coalesced parse — regression guard", () => {
const path = join(__dirname, "..", "..", "..", "ui", "src", "components", "markdown.tsx")
const helper = join(__dirname, "..", "..", "..", "ui", "src", "kilocode", "markdown-stream-highlight.ts")
const stripComments = (src: string): string =>
src
.replace(/\/\*[\s\S]*?\*\//g, "")
.replace(/\{\/\*[\s\S]*?\*\/\}/g, "")
.replace(/^\s*\/\/.*$/gm, "")
const src = stripComments(readFileSync(path, "utf8"))
const body = stripComments(readFileSync(helper, "utf8"))
it("render effect uses requestAnimationFrame to coalesce parses", () => {
// Locate the createEffect that owns the morphdom call.
const match = src.match(/createEffect\s*\(\s*\(\s*\)\s*=>\s*\{[\s\S]*?morphdom\s*\([\s\S]*?^\s*\}\s*\)/m)
expect(match, "render createEffect must contain a morphdom call").toBeTruthy()
const body = match![0]
expect(body).toMatch(/requestAnimationFrame/)
})
it("cleans up the queued frame on dispose", () => {
expect(src).toMatch(/cancelAnimationFrame/)
})
it("exposes a pending frame/content state scoped to the component", () => {
// Any of these forms count. We just need the state to exist so that
// rapid updates can collapse into it.
expect(src).toMatch(/\b(pendingFrame|pendingContent)\b/)
})
it("delegates streamed Shiki refreshes to the Kilo-owned helper", () => {
expect(src).toContain("preserveStreamingHighlight(fromEl, toEl, local.streaming ?? false)")
expect(body).toContain("export function preserveStreamingHighlight")
expect(body).toMatch(/continues\(before, after\)[\s\S]*queue\(from, after, lang\)/)
expect(body).toMatch(/const done = \(\) => \{\s*job\.busy = false\s*if \(!pre\.isConnected\) return/)
})
})
@@ -2,6 +2,7 @@ import { describe, expect, it } from "bun:test"
import path from "node:path"
const WEBVIEW = path.resolve(import.meta.dir, "../../webview-ui")
const WORKER_URL = path.resolve(import.meta.dir, "../setup/worker-url.ts")
const PASS = "TRANSCRIPT_PARTS_PASS"
const FAIL = "TRANSCRIPT_PARTS_FAIL:"
@@ -61,7 +62,7 @@ const SCRIPT = `
describe("transcript parts", () => {
it("keeps timeline candidates aligned with visible transcript parts", () => {
const result = Bun.spawnSync(["bun", "--conditions=browser", "-e", SCRIPT], {
const result = Bun.spawnSync(["bun", "--preload", WORKER_URL, "--conditions=browser", "-e", SCRIPT], {
cwd: WEBVIEW,
stdout: "pipe",
stderr: "pipe",
@@ -30,19 +30,16 @@ describe("prompt.ts Kilo-specific invariants", () => {
expect(content).toContain("Suggestion.dismissAll")
})
test("dismissAll for suggestions and questions runs before enqueue, without cancelling the in-flight fiber", () => {
test("enqueue reserves the follow-up before dismissing blockers, without cancelling the in-flight fiber", () => {
const content = fs.readFileSync(PROMPT_FILE, "utf-8")
// dismissAll for both suggestions and questions must precede the enqueue so
// an in-flight handle.process blocked on a pending tool prompt can return.
// Critically, the block must NOT call state.cancel or KiloSessionPromptQueue.reserve —
// either of those would abort the running streamText mid-tokens, which was
// the #9332 regression. Order: dismissAll(Suggestion), question.dismissAll, enqueue.
// Register the queued follow-up before dismissing blockers so the old turn
// observes hasFollowup when the question resumes. The enqueue reservation
// runs both dismissals before waiting for the prior queue tail.
const block = content.match(
/kilocode_change start[^\n]*unblock tools[\s\S]*?Suggestion\.dismissAll[\s\S]*?question\.dismissAll[\s\S]*?KiloSessionPromptQueue\.enqueue/,
/kilocode_change start[^\n]*register the queued follow-up[\s\S]*?Suggestion\.dismissAll[\s\S]*?question\.dismissAll[\s\S]*?KiloSessionPromptQueue\.enqueue\([\s\S]*?dismiss/,
)
expect(block).not.toBeNull()
expect(content).not.toMatch(/state\.cancel\(input\.sessionID\)/)
expect(content).not.toMatch(/KiloSessionPromptQueue\.reserve/)
})
test("runLoop breaks out between LLM steps when a newer prompt was enqueued", () => {
@@ -4,6 +4,7 @@ import fs from "fs/promises"
import os from "os"
import { Effect } from "effect"
import { Flag } from "@opencode-ai/core/flag/flag"
import { AppRuntime } from "../../src/effect/app-runtime"
import { MessageV2 } from "../../src/session/message-v2"
import { Session } from "../../src/session/session"
import { SessionPrompt } from "../../src/session/prompt"
@@ -26,6 +27,7 @@ beforeAll(async () => {
})
afterAll(async () => {
await AppRuntime.dispose()
await disposeTestRuntime()
Flag.KILO_DB = previous
await Promise.all([dbfile, `${dbfile}-wal`, `${dbfile}-shm`].map(cleanup))
+4
View File
@@ -51,6 +51,10 @@ const testAllow: Record<string, { count: number; reason: string }> = {
},
"kilocode/session/platform-attribution.test.ts": { count: 2, reason: "existing runtime integration test" },
"kilocode/session-prompt-queue.test.ts": { count: 6, reason: "prompt queue legacy instance bridge regression" },
"kilocode/session-prompt-steering.test.ts": {
count: 2,
reason: "disk-backed prompt steering integration test cleanup",
},
"server/experimental-session-list.test.ts": { count: 2, reason: "Kilo session list integration test" },
"kilocode/server/cloud-session-import.test.ts": { count: 5, reason: "full app cloud import transaction integration" },
"kilocode/server/listener-runtime.test.ts": { count: 4, reason: "listener and AppRuntime integration test" },