Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/installer-process-check.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
File /oname=$PLUGINSDIR\windows-installer-migration-journal.ps1 "${PROJECT_DIR}\build\windows-installer-migration-journal.ps1"
File /oname=$PLUGINSDIR\windows-installer-migration-filesystem.ps1 "${PROJECT_DIR}\build\windows-installer-migration-filesystem.ps1"
File /oname=$PLUGINSDIR\windows-installer-migration-actions.ps1 "${PROJECT_DIR}\build\windows-installer-migration-actions.ps1"
File /oname=$PLUGINSDIR\windows-installer-migration-recovery-env.ps1 "${PROJECT_DIR}\build\windows-installer-migration-recovery-env.ps1"
File /oname=$PLUGINSDIR\windows-installer-migration-transaction.ps1 "${PROJECT_DIR}\build\windows-installer-migration-transaction.ps1"
StrCpy $KunInstallerHelperPath "$PLUGINSDIR\kun-windows-installer-migration.ps1"
System::Call 'kernel32::GetCurrentProcessId() i .r0'
Expand Down
1 change: 1 addition & 0 deletions build/installer.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Var /GLOBAL KunInstallerStopDiagnosticPath
File /oname=$PLUGINSDIR\windows-installer-migration-journal.ps1 "${PROJECT_DIR}\build\windows-installer-migration-journal.ps1"
File /oname=$PLUGINSDIR\windows-installer-migration-filesystem.ps1 "${PROJECT_DIR}\build\windows-installer-migration-filesystem.ps1"
File /oname=$PLUGINSDIR\windows-installer-migration-actions.ps1 "${PROJECT_DIR}\build\windows-installer-migration-actions.ps1"
File /oname=$PLUGINSDIR\windows-installer-migration-recovery-env.ps1 "${PROJECT_DIR}\build\windows-installer-migration-recovery-env.ps1"
File /oname=$PLUGINSDIR\windows-installer-migration-transaction.ps1 "${PROJECT_DIR}\build\windows-installer-migration-transaction.ps1"
StrCpy $KunInstallerHelperPath "$PLUGINSDIR\kun-windows-installer-migration.ps1"
StrCpy $KunInstallerResultPath "$PLUGINSDIR\kun-windows-installer-result.txt"
Expand Down
6 changes: 5 additions & 1 deletion build/windows-installer-migration-paths.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
function Get-EnvironmentValue([string]$Name) {
return [Environment]::GetEnvironmentVariable($Name, 'Process')
$value = [Environment]::GetEnvironmentVariable($Name, 'Process')
if ($null -eq $value) {
return ''
}
return [string]$value
}

function Get-CanonicalLeaf {
Expand Down
4 changes: 4 additions & 0 deletions kun/scripts/run-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ function resolveElectronExecutable() {

const vitest = join(dirname(fileURLToPath(import.meta.url)), '..', 'node_modules', 'vitest', 'vitest.mjs')
const doctorFiles = [
'src/adapters/hybrid/hybrid-thread-store.test.ts',
'src/adapters/hybrid/hybrid-usage-backfill-sqlite.test.ts',
'tests/hybrid-store.test.ts',
'tests/thread-store-doctor.test.ts',
'tests/thread-store-doctor-race.test.ts',
'src/manager/usage-query-runner.test.ts',
'src/services/opencode-go-local-quota.test.ts'
]

Expand Down
5 changes: 4 additions & 1 deletion kun/scripts/runtime-build-manifest.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
test('runtime build identity is stable for identical output and changes with emitted JavaScript', async () => {
const root = await mkdtemp(join(tmpdir(), 'kun-runtime-build-manifest-'))
try {
const packageVersion = JSON.parse(
await readFile(new URL('../package.json', import.meta.url), 'utf8')
).version
await writeFile(join(root, 'a.js'), 'export const a = 1\n', 'utf8')
await writeFile(join(root, 'b.js'), 'export const b = 2\n', 'utf8')
const first = await computeRuntimeBuildId(root)
Expand All @@ -31,7 +34,7 @@ test('runtime build identity is stable for identical output and changes with emi
assert.equal(manifest.buildId, await computeRuntimeBuildId(root))
assert.equal(
manifest.serviceVersion,
process.env.KUN_APP_VERSION || process.env.KUN_RELEASE_VERSION || '0.3.8'
process.env.KUN_APP_VERSION || process.env.KUN_RELEASE_VERSION || packageVersion
)
assert.equal(
manifest.channel,
Expand Down
7 changes: 6 additions & 1 deletion kun/src/adapters/file/file-session-store.ordering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { FileSessionStore, readLatestItemsFromJsonl } from './file-session-store
const roots: string[] = []

afterEach(async () => {
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })))
await Promise.all(roots.splice(0).map((root) => rm(root, {
recursive: true,
force: true,
maxRetries: process.platform === 'win32' ? 5 : 0,
retryDelay: 50
})))
})

describe('FileSessionStore item ordering', () => {
Expand Down
62 changes: 61 additions & 1 deletion kun/src/adapters/hybrid/hybrid-thread-store-migrations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,67 @@
import type { Database as BetterSqliteDatabase } from 'better-sqlite3'
import { addColumnIfMissing } from './hybrid-thread-support.js'

export function migrateHybridThreadSchema(db: BetterSqliteDatabase): void {
export function migrateHybridThreadStore(db: BetterSqliteDatabase): void {
db.exec(`
CREATE TABLE IF NOT EXISTS threads (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
workspace TEXT NOT NULL,
model TEXT NOT NULL,
agent_surface TEXT,
mode TEXT NOT NULL,
status TEXT NOT NULL,
approval_policy TEXT NOT NULL,
sandbox_mode TEXT NOT NULL,
approval_reviewer TEXT NOT NULL DEFAULT 'user',
model_request_capture_enabled INTEGER NOT NULL DEFAULT 0,
cost_budget_usd REAL,
cost_budget_warning_sent INTEGER,
relation TEXT NOT NULL,
parent_thread_id TEXT,
forked_from_thread_id TEXT,
forked_from_title TEXT,
forked_at TEXT,
forked_from_message_count INTEGER,
forked_from_turn_count INTEGER,
goal_json TEXT,
todos_json TEXT,
extension_metadata_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
preview TEXT,
message_count INTEGER NOT NULL DEFAULT 0,
event_seq_high_water INTEGER NOT NULL DEFAULT 0,
usage_backfilled INTEGER NOT NULL DEFAULT 0,
usage_backfill_high_water INTEGER NOT NULL DEFAULT 0,
metadata_path TEXT NOT NULL,
messages_path TEXT NOT NULL,
events_path TEXT NOT NULL,
search_text TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS threads_updated_idx
ON threads(updated_at_ms DESC, id DESC);
CREATE INDEX IF NOT EXISTS threads_workspace_updated_idx
ON threads(workspace, updated_at_ms DESC, id DESC);
CREATE INDEX IF NOT EXISTS threads_status_updated_idx
ON threads(status, updated_at_ms DESC, id DESC);
CREATE INDEX IF NOT EXISTS threads_relation_updated_idx
ON threads(relation, updated_at_ms DESC, id DESC);
CREATE TABLE IF NOT EXISTS usage_events (
thread_id TEXT NOT NULL,
seq INTEGER NOT NULL,
timestamp TEXT NOT NULL,
turn_id TEXT, model TEXT, provider_id TEXT,
usage_json TEXT NOT NULL,
PRIMARY KEY(thread_id, seq)
);
CREATE INDEX IF NOT EXISTS usage_events_thread_seq_idx
ON usage_events(thread_id, seq);
CREATE INDEX IF NOT EXISTS usage_events_timestamp_idx
ON usage_events(timestamp);
`)
addColumnIfMissing(db, 'threads', 'todos_json TEXT')
addColumnIfMissing(db, 'threads', 'extension_metadata_json TEXT')
addColumnIfMissing(db, 'threads', 'model_request_capture_enabled INTEGER NOT NULL DEFAULT 0')
Expand Down
69 changes: 2 additions & 67 deletions kun/src/adapters/hybrid/hybrid-thread-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { UsageQueryExecutor } from '../../manager/usage-query-executor.js'
import { UsageIndexUnavailableError } from '../../manager/usage-errors.js'
import { JsonlFileAccessCoordinator } from '../file/jsonl-file-access.js'
import { renameFileWithRetry } from '../file/atomic-write.js'
import { migrateHybridThreadSchema } from './hybrid-thread-store-migrations.js'
import { migrateHybridThreadStore } from './hybrid-thread-store-migrations.js'

export { describeSqliteAbiMismatch } from './hybrid-thread-support.js'

Expand Down Expand Up @@ -373,7 +373,7 @@ export class HybridThreadStore implements ThreadStore {
this.db.pragma('journal_mode = WAL')
this.db.pragma('busy_timeout = 5000')
this.db.pragma('foreign_keys = ON')
this.migrate()
migrateHybridThreadStore(this.db)
this.index = new HybridThreadIndexRepository(this.db, (threadId) => ({
metadataPath: this.metadataPath(threadId), messagesPath: this.messagesPath(threadId),
eventsPath: this.eventsPath(threadId)
Expand Down Expand Up @@ -417,71 +417,6 @@ export class HybridThreadStore implements ThreadStore {
}
}

private migrate(): void {
if (!this.db) return
this.db.exec(`
CREATE TABLE IF NOT EXISTS threads (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
workspace TEXT NOT NULL,
model TEXT NOT NULL,
agent_surface TEXT,
mode TEXT NOT NULL,
status TEXT NOT NULL,
approval_policy TEXT NOT NULL,
sandbox_mode TEXT NOT NULL,
approval_reviewer TEXT NOT NULL DEFAULT 'user',
model_request_capture_enabled INTEGER NOT NULL DEFAULT 0,
cost_budget_usd REAL,
cost_budget_warning_sent INTEGER,
relation TEXT NOT NULL,
parent_thread_id TEXT,
forked_from_thread_id TEXT,
forked_from_title TEXT,
forked_at TEXT,
forked_from_message_count INTEGER,
forked_from_turn_count INTEGER,
goal_json TEXT,
todos_json TEXT,
extension_metadata_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
created_at_ms INTEGER NOT NULL,
updated_at_ms INTEGER NOT NULL,
preview TEXT,
message_count INTEGER NOT NULL DEFAULT 0,
event_seq_high_water INTEGER NOT NULL DEFAULT 0,
usage_backfilled INTEGER NOT NULL DEFAULT 0,
usage_backfill_high_water INTEGER NOT NULL DEFAULT 0,
metadata_path TEXT NOT NULL,
messages_path TEXT NOT NULL,
events_path TEXT NOT NULL,
search_text TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS threads_updated_idx
ON threads(updated_at_ms DESC, id DESC);
CREATE INDEX IF NOT EXISTS threads_workspace_updated_idx
ON threads(workspace, updated_at_ms DESC, id DESC);
CREATE INDEX IF NOT EXISTS threads_status_updated_idx
ON threads(status, updated_at_ms DESC, id DESC);
CREATE INDEX IF NOT EXISTS threads_relation_updated_idx
ON threads(relation, updated_at_ms DESC, id DESC);
CREATE TABLE IF NOT EXISTS usage_events (
thread_id TEXT NOT NULL,
seq INTEGER NOT NULL,
timestamp TEXT NOT NULL,
turn_id TEXT, model TEXT, provider_id TEXT,
usage_json TEXT NOT NULL,
PRIMARY KEY(thread_id, seq)
);
CREATE INDEX IF NOT EXISTS usage_events_thread_seq_idx
ON usage_events(thread_id, seq);
CREATE INDEX IF NOT EXISTS usage_events_timestamp_idx
ON usage_events(timestamp);
`)
migrateHybridThreadSchema(this.db)
}

private cachedStatement(sql: string): Statement {
if (!this.db) throw new Error('sqlite unavailable')
let statement = this.statementCache.get(sql)
Expand Down
19 changes: 16 additions & 3 deletions kun/src/adapters/tool/builtin-bash-session-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ export const DEFAULT_MAX_RUNNING_BACKGROUND_BASH_SESSIONS = 32
export const DEFAULT_MAX_RUNNING_BACKGROUND_BASH_SESSIONS_PER_THREAD = 4
export const DEFAULT_MAX_BACKGROUND_BASH_TIMEOUT_SECONDS = DEFAULT_BACKGROUND_BASH_TIMEOUT_SECONDS

export function terminateBashProcessTree(child: ChildProcess): Promise<void> {
terminateSpawnTree(child)
return new Promise((resolve, reject) => {
export async function terminateBashProcessTree(child: ChildProcess): Promise<void> {
const treeTerminator = terminateSpawnTree(child)
await new Promise<void>((resolve, reject) => {
const startedAt = Date.now()
let forced = false
const waitOrForce = () => {
Expand All @@ -40,6 +40,19 @@ export function terminateBashProcessTree(child: ChildProcess): Promise<void> {
}
setTimeout(waitOrForce, 25)
})
if (treeTerminator && treeTerminator.exitCode === null && treeTerminator.signalCode === null) {
await new Promise<void>((resolve) => {
const timer = setTimeout(resolve, STOP_GRACE_MS)
treeTerminator.once('close', () => {
clearTimeout(timer)
resolve()
})
treeTerminator.once('error', () => {
clearTimeout(timer)
resolve()
})
})
}
}

export function bashProcessTreeIsAlive(child: ChildProcess): boolean {
Expand Down
11 changes: 6 additions & 5 deletions kun/src/adapters/tool/builtin-shell-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,12 @@ export function terminateSpawnTree(
signal?: NodeJS.Signals
spawnImpl?: SpawnLike
} = {}
): void {
): ChildProcess | undefined {
const signal = options.signal ?? 'SIGTERM'
const pid = child.pid
if (!pid) {
child.kill(signal)
return
return undefined
}

if ((options.platform ?? process.platform) === 'win32') {
Expand All @@ -465,18 +465,19 @@ export function terminateSpawnTree(
child.kill(signal)
})
taskkill.unref?.()
return
return taskkill
} catch {
child.kill(signal)
return
return undefined
}
}

try {
process.kill(-pid, signal)
return
return undefined
} catch {
child.kill(signal)
return undefined
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ describe('show_diagram provider', () => {
launcher: 'diagram_design', profile: 'diagram-designer', agentSurface: 'design',
sandboxMode: 'workspace-write', security: { memoryEnabled: false }
})
expect(calls[0]?.workspace).toEqual(expect.stringContaining('/.kun-design/diagram-prototypes/'))
expect(calls[0]?.workspace).toEqual(expect.stringContaining(join('.kun-design', 'diagram-prototypes')))
expect(result).toMatchObject({ output: { status: 'completed', diagramPrototype: {
producer: 'diagram-designer', profile: 'diagram-designer', childId: 'child-diagram',
summary: 'Generated the release diagram.'
Expand Down
2 changes: 1 addition & 1 deletion kun/src/adapters/tool/extension-tool-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ describe('ExtensionToolRegistry', () => {
)

firstB.dispose()
expect(() => firstHost.listTools(context({ extensionToolCatalogEpoch: firstEpoch }))).toThrow(
await expect(firstHost.listTools(context({ extensionToolCatalogEpoch: firstEpoch }))).rejects.toThrow(
ExtensionToolCatalogDriftError
)
})
Expand Down
13 changes: 9 additions & 4 deletions kun/src/adapters/tool/github-mcp-credential.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createHash } from 'node:crypto'
import { execFile } from 'node:child_process'
import { constants, accessSync, realpathSync, statSync } from 'node:fs'
import { delimiter, posix, win32 } from 'node:path'
import { posix, win32 } from 'node:path'
import type { McpServerConfig } from '../../contracts/capabilities.js'
import {
KUN_GITHUB_PAT_ENV_VAR,
Expand Down Expand Up @@ -207,11 +207,16 @@ function githubCliExecutableCandidates(platform: NodeJS.Platform, env: NodeJS.Pr
}

function githubCliPathCandidates(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): string[] {
if (!env.PATH) return []
const pathValue = env.PATH
if (!pathValue) return []
const pathApi = platform === 'win32' ? win32 : posix
const executable = platform === 'win32' ? 'gh.exe' : 'gh'
return [...new Set(env.PATH.split(platform === 'win32' ? win32.delimiter : delimiter).map((item) => item.trim()).filter(Boolean))]
.map((directory) => pathApi.join(directory, executable))
const pathDelimiter = platform === 'win32' ? win32.delimiter : posix.delimiter
const directories = pathValue
.split(pathDelimiter)
.map((entry) => entry.trim())
.filter(Boolean)
return [...new Set(directories)].map((directory) => pathApi.join(directory, executable))
}

function windowsGitHubCliFallbackCandidates(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): string[] {
Expand Down
Loading
Loading