mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-10-29 18:52:45 +08:00
fix: align merged CI contracts
This commit is contained in:
@@ -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,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))
|
||||
|
||||
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user