mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-17 17:43:04 +08:00
Merge remote-tracking branch 'origin/main' into maphew/main
# Conflicts: # packages/opencode/src/kilocode/review/local-review-uncommitted.txt # packages/opencode/src/kilocode/review/review.txt # packages/opencode/test/kilocode/local-review-command.test.ts # packages/opencode/test/session/prompt.test.ts
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bun
|
||||
// kilocode_change - new file
|
||||
|
||||
// This is a CI-only architecture test, not production network enforcement. Model tools run
|
||||
// inside the trusted kilo serve process, so macOS Seatbelt can only confine their spawned
|
||||
// children. In-process tools must use the policy-aware HTTP capability instead of direct fetch,
|
||||
// sockets, or ad hoc clients. Keep this narrow scan to prevent future tool implementations from
|
||||
// accidentally bypassing that boundary; trusted provider and model-inference code is intentionally
|
||||
// outside the scanned directories. Runtime enforcement remains in @kilocode/sandbox.
|
||||
|
||||
import path from "node:path"
|
||||
import { opaque } from "../packages/opencode/src/kilocode/sandbox/network-tools"
|
||||
|
||||
const root = path.resolve(import.meta.dir, "..")
|
||||
const source = path.join(root, "packages", "opencode", "src")
|
||||
const dirs = ["tool", "kilocode/tool", "mcp"]
|
||||
const checks = [
|
||||
{ name: "direct fetch", pattern: /\b(?:globalThis\.)?fetch\s*\(/g },
|
||||
{ name: "raw FetchHttpClient layer", pattern: /\bFetchHttpClient\.layer\b/g },
|
||||
{ name: "direct Bun socket", pattern: /\bBun\.(?:connect|udpSocket)\s*\(/g },
|
||||
{
|
||||
name: "raw network module",
|
||||
pattern:
|
||||
/\bfrom\s+["'](?:(?:node:)?(?:http|https|http2|net|tls|dgram)(?:\/[^"']*)?|(?:undici|axios|got)(?:\/[^"']*)?)["']/g,
|
||||
},
|
||||
{
|
||||
name: "dynamic network module",
|
||||
pattern:
|
||||
/\b(?:require|import)\s*\(\s*["'](?:(?:node:)?(?:http|https|http2|net|tls|dgram)(?:\/[^"']*)?|(?:undici|axios|got)(?:\/[^"']*)?)["']/g,
|
||||
},
|
||||
{
|
||||
name: "ad hoc network client",
|
||||
pattern:
|
||||
/\bnew\s+(?:WarpGrepClient|OpenAI|QdrantClient|BedrockRuntimeClient|WebSocket|EventSource|StreamableHTTPClientTransport|SSEClientTransport)\s*\(/g,
|
||||
},
|
||||
]
|
||||
const allow = new Map([
|
||||
...opaque.flatMap((item) =>
|
||||
"client" in item
|
||||
? [[`${item.file}:${item.client.name}`, { ...item.client, file: item.file, id: item.id }] as const]
|
||||
: [],
|
||||
),
|
||||
[
|
||||
"mcp/index.ts:ad hoc network client",
|
||||
{
|
||||
count: 3,
|
||||
file: "mcp/index.ts",
|
||||
id: "remote_mcp",
|
||||
reason: "MCP SDK transports are classified as remote delegated authority before model execution",
|
||||
},
|
||||
] as const,
|
||||
])
|
||||
const excluded = new Map([
|
||||
["mcp/oauth-callback.ts", "OAuth callback listener is trusted MCP control-plane setup, not model tool execution"],
|
||||
])
|
||||
const hits: Array<{ file: string; name: string; line: number }> = []
|
||||
const glob = new Bun.Glob("**/*.ts")
|
||||
|
||||
for (const dir of dirs) {
|
||||
for (const file of glob.scanSync({ cwd: path.join(source, dir), onlyFiles: true })) {
|
||||
const rel = path.posix.join(dir, file.replaceAll("\\", "/"))
|
||||
if (excluded.has(rel)) continue
|
||||
const text = await Bun.file(path.join(source, rel)).text()
|
||||
for (const check of checks) {
|
||||
for (const match of text.matchAll(check.pattern)) {
|
||||
hits.push({
|
||||
file: rel,
|
||||
name: check.name,
|
||||
line: text.slice(0, match.index ?? 0).split("\n").length,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const invalid = hits.filter((hit) => !allow.has(`${hit.file}:${hit.name}`))
|
||||
const clients = [...allow.entries()].flatMap(([key, entry]) => {
|
||||
const split = key.lastIndexOf(":")
|
||||
const file = key.slice(0, split)
|
||||
const name = key.slice(split + 1)
|
||||
const count = hits.filter((hit) => hit.file === file && hit.name === name).length
|
||||
if (count === entry.count) return []
|
||||
return [` packages/opencode/src/${file}: expected ${entry.count} ${name} site(s), found ${count} (${entry.reason})`]
|
||||
})
|
||||
const tools = (
|
||||
await Promise.all(
|
||||
opaque.map(async (item) => {
|
||||
const text = await Bun.file(path.join(source, item.file)).text()
|
||||
const id = item.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
if (new RegExp(`Tool\\.define\\(\\s*["']${id}["']`).test(text)) return []
|
||||
return [` packages/opencode/src/${item.file}: opaque classification must match Tool.define("${item.id}")`]
|
||||
}),
|
||||
)
|
||||
).flat()
|
||||
const drift = [...clients, ...tools]
|
||||
|
||||
const network = await Bun.file(path.join(source, "kilocode", "sandbox", "network.ts")).text()
|
||||
const registry = await Bun.file(path.join(source, "tool", "registry.ts")).text()
|
||||
const session = await Bun.file(path.join(source, "session", "tools.ts")).text()
|
||||
const mcp = await Bun.file(path.join(source, "mcp", "index.ts")).text()
|
||||
const structure = [
|
||||
...(!network.includes('import { opaque } from "./network-tools"') ||
|
||||
!network.includes("opaque.map((item) => item.id)")
|
||||
? [" kilocode/sandbox/network.ts must derive runtime opaque tool IDs from network-tools.ts"]
|
||||
: []),
|
||||
...(!registry.includes("Layer.provide(ToolNetwork.httpLayer)")
|
||||
? [" tool/registry.ts must provide the policy-aware ToolNetwork HTTP layer"]
|
||||
: []),
|
||||
...(registry.includes("FetchHttpClient.layer")
|
||||
? [" tool/registry.ts must not provide a raw FetchHttpClient layer"]
|
||||
: []),
|
||||
...(!registry.includes("ToolNetwork.builtin(result)")
|
||||
? [" tool/registry.ts must distinguish built-in tools from untrusted custom tools"]
|
||||
: []),
|
||||
...(!/SandboxPolicy\.executeTool\(\s*ctx\.sessionID,\s*item,/.test(session)
|
||||
? [" session/tools.ts must route built-in and custom tools through session-aware executeTool"]
|
||||
: []),
|
||||
...(!mcp.includes("SandboxNetwork.remote(tool)")
|
||||
? [" mcp/index.ts must classify remote MCP delegated authority"]
|
||||
: []),
|
||||
...(!/SandboxPolicy\.executeMcp\(\s*ctx\.sessionID,\s*item,/.test(session)
|
||||
? [" session/tools.ts must route MCP delegated authority through session-aware executeMcp"]
|
||||
: []),
|
||||
]
|
||||
|
||||
if (invalid.length > 0 || drift.length > 0 || structure.length > 0) {
|
||||
if (invalid.length > 0) {
|
||||
console.error("Found model-tool network clients that bypass the sandbox capability:")
|
||||
for (const hit of invalid) console.error(` packages/opencode/src/${hit.file}:${hit.line} (${hit.name})`)
|
||||
console.error("")
|
||||
}
|
||||
if (drift.length > 0) {
|
||||
console.error("Classified model-tool network exceptions no longer match source:")
|
||||
for (const item of drift) console.error(item)
|
||||
console.error("")
|
||||
}
|
||||
if (structure.length > 0) {
|
||||
console.error("Model-tool network boundary wiring is incomplete:")
|
||||
for (const item of structure) console.error(item)
|
||||
console.error("")
|
||||
}
|
||||
console.error(
|
||||
"Use the @kilocode/sandbox network capability or classify an opaque client at the common tool boundary.",
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(
|
||||
`check-model-tool-network: ${hits.length} classified client site(s), policy-aware tool and MCP boundaries verified.`,
|
||||
)
|
||||
@@ -50,6 +50,7 @@ const EXEMPT_SCOPES = [
|
||||
"script/check-opencode-annotations.ts",
|
||||
"packages/script/tests/check-opencode-annotations.test.ts",
|
||||
".github/workflows/check-opencode-annotations.yml",
|
||||
".github/workflows/watch-opencode-releases.yml",
|
||||
]
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
@@ -29,18 +29,13 @@ const allow: Record<string, string> = {
|
||||
}
|
||||
|
||||
const testAllow: Record<string, { count: number; reason: string }> = {
|
||||
"control-plane/workspace.test.ts": { count: 5, reason: "existing runtime integration test" },
|
||||
"kilocode/config-resilience.test.ts": { count: 4, reason: "existing runtime integration test" },
|
||||
"kilocode/config-validation.test.ts": { count: 2, reason: "existing runtime integration test" },
|
||||
"kilocode/plan-followup.test.ts": { count: 4, reason: "existing runtime integration test" },
|
||||
"kilocode/session/platform-attribution.test.ts": { count: 5, reason: "existing runtime integration test" },
|
||||
"kilocode/session/platform-attribution.test.ts": { count: 2, reason: "existing runtime integration test" },
|
||||
"kilocode/session-prompt-queue.test.ts": { count: 5, reason: "prompt queue legacy instance bridge regression" },
|
||||
"kilocode/session/session.test.ts": { count: 5, reason: "existing runtime integration test" },
|
||||
"provider/amazon-bedrock.test.ts": { count: 2, reason: "existing runtime integration test" },
|
||||
"provider/provider.test.ts": { count: 3, reason: "existing runtime integration test" },
|
||||
"server/experimental-session-list.test.ts": { count: 2, reason: "Kilo session list integration test" },
|
||||
"server/httpapi-event.test.ts": { count: 6, reason: "event stream integration test" },
|
||||
"session/llm.test.ts": { count: 2, reason: "existing runtime integration test" },
|
||||
"kilocode/server/listener-runtime.test.ts": { count: 3, reason: "listener and AppRuntime integration test" },
|
||||
"tool/recall.test.ts": { count: 11, reason: "existing runtime integration test" },
|
||||
}
|
||||
|
||||
|
||||
@@ -86,13 +86,14 @@ const SKIP_DIRS = ["node_modules", ".storybook", "stories", "test", "tests", "__
|
||||
const SKIP_PATH_SEGMENTS = ["continuedev"]
|
||||
|
||||
// Individual files to skip (data files full of non-user-facing URLs)
|
||||
const SKIP_FILES = ["models-snapshot.ts", "models-snapshot.js", "check-forbidden-strings.ts"] // kilocode_change
|
||||
const SKIP_FILES = ["check-forbidden-strings.ts"] // kilocode_change
|
||||
|
||||
function shouldExclude(url: string): boolean {
|
||||
return EXCLUDE_PATTERNS.some((re) => re.test(url))
|
||||
}
|
||||
|
||||
function shouldSkipFile(filepath: string): boolean {
|
||||
if (filepath === "packages/opencode/src/cli/cmd/account.ts") return true // kilocode_change - command is not registered in Kilo
|
||||
const rel = path.relative(ROOT, filepath)
|
||||
const parts = rel.split(path.sep)
|
||||
if (parts.some((p) => SKIP_DIRS.includes(p))) return true
|
||||
|
||||
@@ -7,5 +7,3 @@ await $`bun ./packages/sdk/js/script/build.ts`
|
||||
await $`bun dev generate > ../sdk/openapi.json`.cwd("packages/opencode")
|
||||
|
||||
await $`bun ./script/generate-cli-docs.ts`
|
||||
|
||||
await $`./script/format.ts`
|
||||
|
||||
+123
-14
@@ -2,14 +2,39 @@
|
||||
|
||||
import path from "node:path"
|
||||
|
||||
const raw = process.argv[2]
|
||||
if (!raw) {
|
||||
console.error("Usage: bun run script/upgrade-opentui.ts <version>")
|
||||
const args = process.argv.slice(2)
|
||||
const usage = "Usage: bun run script/upgrade-opentui.ts [--snapshot] <version>"
|
||||
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
console.log(usage)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const snapshotArg = args.find((arg) => arg.startsWith("--snapshot="))
|
||||
const snapshot = args.includes("--snapshot") || snapshotArg !== undefined
|
||||
const unknown = args.find((arg) => arg.startsWith("-") && arg !== "--snapshot" && !arg.startsWith("--snapshot="))
|
||||
if (unknown) {
|
||||
console.error(`Unknown option: ${unknown}`)
|
||||
console.error(usage)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const positional = args.filter((arg) => arg !== "--snapshot" && !arg.startsWith("--snapshot="))
|
||||
const raw = snapshotArg?.slice("--snapshot=".length) || positional[0]
|
||||
if (!raw || positional.length > (snapshotArg ? 0 : 1)) {
|
||||
console.error(usage)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (snapshotArg === "--snapshot=") {
|
||||
console.error("Missing snapshot version")
|
||||
console.error(usage)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const ver = raw.replace(/^v/, "")
|
||||
const root = path.resolve(import.meta.dir, "..")
|
||||
const lockfile = path.join(root, "bun.lock")
|
||||
const skip = new Set([".git", ".opencode", ".turbo", "dist", "node_modules"])
|
||||
const keys = ["@opentui/core", "@opentui/keymap", "@opentui/solid"] as const
|
||||
|
||||
@@ -17,22 +42,24 @@ const files = (await Array.fromAsync(new Bun.Glob("**/package.json").scan({ cwd:
|
||||
(file) => !file.split("/").some((part) => skip.has(part)),
|
||||
)
|
||||
|
||||
const setVersion = (cur: string) => {
|
||||
const setVersion = (cur: string, kind: "dep" | "peer") => {
|
||||
if (cur === "catalog:" || cur.startsWith("workspace:")) return cur
|
||||
if (snapshot) return ver
|
||||
if (kind === "peer") return `>=${ver}`
|
||||
if (cur.startsWith(">=")) return `>=${ver}`
|
||||
if (cur.startsWith("^")) return `^${ver}`
|
||||
if (cur.startsWith("~")) return `~${ver}`
|
||||
return ver
|
||||
}
|
||||
|
||||
const editDeps = (obj: unknown) => {
|
||||
const editDeps = (obj: unknown, kind: "dep" | "peer") => {
|
||||
if (!obj || typeof obj !== "object") return false
|
||||
const map = obj as Record<string, unknown>
|
||||
return keys
|
||||
.map((key) => {
|
||||
const cur = map[key]
|
||||
if (typeof cur !== "string") return false
|
||||
const next = setVersion(cur)
|
||||
const next = setVersion(cur, kind)
|
||||
if (next === cur) return false
|
||||
map[key] = next
|
||||
return true
|
||||
@@ -53,6 +80,21 @@ const editCatalog = (obj: unknown) => {
|
||||
.some(Boolean)
|
||||
}
|
||||
|
||||
const editOverrides = (obj: unknown) => {
|
||||
if (!obj || typeof obj !== "object") return false
|
||||
const map = obj as Record<string, unknown>
|
||||
return keys
|
||||
.map((key) => {
|
||||
const cur = map[key]
|
||||
if (typeof cur !== "string") return false
|
||||
const next = snapshot ? ver : "catalog:"
|
||||
if (next === cur) return false
|
||||
map[key] = next
|
||||
return true
|
||||
})
|
||||
.some(Boolean)
|
||||
}
|
||||
|
||||
const out = (
|
||||
await Promise.all(
|
||||
files.map(async (rel) => {
|
||||
@@ -61,9 +103,10 @@ const out = (
|
||||
const json = JSON.parse(txt)
|
||||
const hit = [
|
||||
editCatalog(json.workspaces?.catalog),
|
||||
editDeps(json.dependencies),
|
||||
editDeps(json.devDependencies),
|
||||
editDeps(json.peerDependencies),
|
||||
editOverrides(json.overrides),
|
||||
editDeps(json.dependencies, "dep"),
|
||||
editDeps(json.devDependencies, "dep"),
|
||||
editDeps(json.peerDependencies, "peer"),
|
||||
].some(Boolean)
|
||||
if (!hit) return null
|
||||
await Bun.write(file, `${JSON.stringify(json, null, 2)}\n`)
|
||||
@@ -73,11 +116,77 @@ const out = (
|
||||
).filter((item): item is string => item !== null)
|
||||
|
||||
if (out.length === 0) {
|
||||
console.log("No opentui deps found")
|
||||
process.exit(0)
|
||||
console.log(`No opentui manifest updates needed for ${ver}`)
|
||||
}
|
||||
|
||||
console.log(`Updated opentui to ${ver} in:`)
|
||||
for (const file of out) {
|
||||
console.log(`- ${file}`)
|
||||
if (out.length > 0) {
|
||||
console.log(`Updated opentui${snapshot ? " snapshot" : ""} to ${ver} in:`)
|
||||
for (const file of out) {
|
||||
console.log(`- ${file}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Running bun install to update bun.lock...")
|
||||
const install = Bun.spawn([process.execPath, "install"], {
|
||||
cwd: root,
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
const installCode = await install.exited
|
||||
if (installCode !== 0) process.exit(installCode)
|
||||
|
||||
const fixed = await fixKnownLockfileIssues()
|
||||
if (fixed.length > 0) {
|
||||
console.log("Removed stale opentui-spinner peer lockfile entries:")
|
||||
for (const item of fixed) {
|
||||
console.log(`- ${item}`)
|
||||
}
|
||||
}
|
||||
|
||||
const stale = await findStaleLockfileEntries()
|
||||
if (stale.length > 0) {
|
||||
console.error(`bun.lock still contains stale opentui versions after upgrading to ${ver}:`)
|
||||
for (const item of stale) {
|
||||
console.error(`- ${item.entry}: ${item.pkg}@${item.version}`)
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log("bun.lock opentui versions are consistent")
|
||||
|
||||
async function fixKnownLockfileIssues() {
|
||||
const txt = await Bun.file(lockfile).text()
|
||||
const stale = findStaleLockfileEntriesInText(txt)
|
||||
if (stale.length === 0) return []
|
||||
if (stale.some((item) => !item.entry.startsWith("opentui-spinner/@opentui/"))) return []
|
||||
|
||||
const removed = txt
|
||||
.split("\n")
|
||||
.map((line) => line.match(/^ "(opentui-spinner\/@opentui\/[^\"]+)": /)?.[1])
|
||||
.filter((item): item is string => item !== undefined)
|
||||
|
||||
if (removed.length === 0) return []
|
||||
|
||||
await Bun.write(
|
||||
lockfile,
|
||||
txt
|
||||
.split("\n")
|
||||
.filter((line) => !line.match(/^ "opentui-spinner\/@opentui\//))
|
||||
.join("\n"),
|
||||
)
|
||||
return removed
|
||||
}
|
||||
|
||||
async function findStaleLockfileEntries() {
|
||||
return findStaleLockfileEntriesInText(await Bun.file(lockfile).text())
|
||||
}
|
||||
|
||||
function findStaleLockfileEntriesInText(txt: string) {
|
||||
return Array.from(txt.matchAll(/^ "([^"]+)": \["(@opentui\/(?:core(?:-[^@"]+)?|keymap|solid))@([^"]+)"/gm))
|
||||
.map((match) => ({
|
||||
entry: match[1]!,
|
||||
pkg: match[2]!,
|
||||
version: match[3]!,
|
||||
}))
|
||||
.filter((item) => item.version !== ver)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/upstream-merge",
|
||||
"version": "7.3.46",
|
||||
"version": "7.3.54",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Scripts for automating upstream opencode merges into Kilo",
|
||||
|
||||
@@ -11,6 +11,16 @@ test("matches removed app package glob paths", () => {
|
||||
expect(shouldSkip("packages/app/package.json", ["packages/app/**"])).toBe(true)
|
||||
})
|
||||
|
||||
test("matches upstream CLI scaffold glob paths", () => {
|
||||
expect(shouldSkip("packages/cli/package.json", ["packages/cli/**"])).toBe(true)
|
||||
expect(shouldSkip("packages/cli/src/index.ts", ["packages/cli/**"])).toBe(true)
|
||||
})
|
||||
|
||||
test("matches upstream stats package glob paths", () => {
|
||||
expect(shouldSkip("packages/stats/app/package.json", ["packages/stats/**"])).toBe(true)
|
||||
expect(shouldSkip("packages/stats/core/src/index.ts", ["packages/stats/**"])).toBe(true)
|
||||
})
|
||||
|
||||
test("matches removed vscode sdk glob paths", () => {
|
||||
expect(shouldSkip("sdks/vscode/package.json", ["sdks/vscode/**"])).toBe(true)
|
||||
expect(shouldSkip("sdks/vscode/src/extension.ts", ["sdks/vscode/**"])).toBe(true)
|
||||
|
||||
@@ -159,6 +159,8 @@ export const defaultConfig: MergeConfig = {
|
||||
"packages/app/**",
|
||||
"packages/desktop/**",
|
||||
"packages/desktop-electron/**",
|
||||
"packages/cli/**",
|
||||
"packages/stats/**",
|
||||
"sdks/vscode/**",
|
||||
// GitHub Action - Kilo version is fully ported and complete
|
||||
"github/index.ts",
|
||||
|
||||
Reference in New Issue
Block a user