Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
47cd55b
feat: multi-instance session lease, write fencing, and fs/watch relia…
7Sageer Jul 19, 2026
82b8da6
fix: close lock and session lifecycle review gaps
7Sageer Jul 20, 2026
1f76353
fix: isolate session redirects and fence task persistence
7Sageer Jul 20, 2026
4845ee1
fix: harden fs watch and session lifecycle races
7Sageer Jul 20, 2026
2d76c12
refactor: replace session lease heartbeat protocol with kernel-held f…
7Sageer Jul 20, 2026
3d52ba5
fix: harden filesystem coordination and shutdown
7Sageer Jul 20, 2026
2762bfc
refactor: drop multi-server flag and dedupe session ownership classif…
7Sageer Jul 21, 2026
dccb293
fix(agent-core-v2): satisfy expect-expect and only-throw-error lint r…
7Sageer Jul 21, 2026
8bf9592
chore(nix): update pnpmDeps hash for kernel-file-lock dependencies
7Sageer Jul 21, 2026
cfaf8eb
ci: drop kernel-file-lock Node 26 job and Node engines caps
7Sageer Jul 21, 2026
5d2d71e
chore: streamline user-facing changeset entries
7Sageer Jul 21, 2026
37e243d
fix: close session event journals before lease release
7Sageer Jul 21, 2026
c4030ef
refactor: simplify write fencing to stat-only detection
7Sageer Jul 21, 2026
6d6a4ad
fix: harden multi-server session transitions
7Sageer Jul 21, 2026
225f7f9
refactor: remove obsolete watch and lock state
7Sageer Jul 21, 2026
fd1cdad
refactor: drop dead ownership surface and dedupe PR helpers
7Sageer Jul 21, 2026
eb7091b
refactor: dedupe lock, fencing, and watch helpers
7Sageer Jul 21, 2026
248e47f
test: prune redundant and low-value coverage
7Sageer Jul 21, 2026
68815ca
fix(agent-core-v2): enforce session lease domain layer
7Sageer Jul 21, 2026
ccd304c
fix(agent-core-v2): keep partial reads behind write fence
7Sageer Jul 21, 2026
504b878
refactor: route skill root probes through host fs
7Sageer Jul 21, 2026
912c039
fix(kap-server): pass host fs to workspace skill root probes
7Sageer Jul 22, 2026
95d92dc
test(agent-core-v2): stub identity realpath in plan fakes
7Sageer Jul 22, 2026
735bbdc
refactor(agent-core-v2): converge session release
7Sageer Jul 22, 2026
9222d02
refactor(agent-core-v2): clarify write admission interfaces
7Sageer Jul 22, 2026
41c1712
fix: handle capped reads, append writes, and IPv6 hosts
7Sageer Jul 22, 2026
d6f1976
refactor: rename session lifecycle properties for clarity and consist…
7Sageer Jul 22, 2026
b58214d
fix(agent-core-v2): isolate file baselines per agent
7Sageer Jul 22, 2026
83d93cd
fix: adapt to upstream veto-event tool API and reconcile fork copy or…
7Sageer Jul 22, 2026
0300890
refactor: re-export kernel file lock binding hook from agent-core-v2
7Sageer Jul 22, 2026
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
5 changes: 5 additions & 0 deletions .changeset/fix-ipv6-web-origin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix `kimi web` startup when binding to an IPv6 host.
5 changes: 5 additions & 0 deletions .changeset/journal-fswatch-reliability-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix `.gitignore` edits not taking effect until restart for file listing and file watching.
5 changes: 5 additions & 0 deletions .changeset/multi-instance-session-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

web: When several server instances share one home, opening a session held by another instance now redirects to that instance, and the session list refreshes automatically when a peer adds or removes sessions.
6 changes: 6 additions & 0 deletions .changeset/reexport-kernel-file-lock-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@moonshot-ai/agent-core-v2": patch
"@moonshot-ai/kimi-code": patch
---

Re-export the kernel file lock binding hook from the agent engine so the CLI no longer depends on the internal lock package directly.
5 changes: 5 additions & 0 deletions .changeset/session-event-release-barrier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

web: Restore live event delivery after archiving and reopening a session.
5 changes: 5 additions & 0 deletions .changeset/session-lease-write-fencing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Opening the same session from a second instance now fails with a clear ownership error, while shutdown blocks late writes and releases ambiguous closes through a dirty fallback.
5 changes: 5 additions & 0 deletions .changeset/skill-hot-reload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Newly added or updated skills now take effect on the next turn without restarting, and the web skill list refreshes automatically.
5 changes: 5 additions & 0 deletions .changeset/stale-write-fencing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Each agent now tracks its own file-read baselines and refuses to overwrite files that changed on disk after its last read.
2 changes: 1 addition & 1 deletion apps/kimi-code/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ agents/
src/generated/vis-web-asset.ts

# Copied from packages/pi-tui/native at build time by scripts/copy-native-assets.mjs
native/
/native/
3 changes: 3 additions & 0 deletions apps/kimi-code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
"e2e:real": "pnpm -w run build:packages && KIMI_E2E_REAL=1 vitest run test/e2e/real-llm-smoke.e2e.test.ts",
"postinstall": "node scripts/postinstall.mjs"
},
"dependencies": {
"fs-native-extensions": "1.5.0"
},
"optionalDependencies": {
"@mariozechner/clipboard": "^0.3.9",
"node-pty": "^1.1.0"
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/scripts/native/check-bundle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const optionalRuntimeRequires = new Set([
'utf-8-validate',
]);
const optionalRelativeRuntimeRequires = new Set(['./crypto/build/Release/sshcrypto.node']);
const handledNativeRuntimeRequires = new Set();
const handledNativeRuntimeRequires = new Set(['fs-native-extensions']);

function isAllowedSpecifier(specifier) {
if (builtins.has(specifier) || specifier.startsWith('node:')) return true;
Expand Down
16 changes: 16 additions & 0 deletions apps/kimi-code/scripts/native/native-deps.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ const piTuiNativeFileByTarget = Object.freeze({
'win32-x64': ['native/win32/prebuilds/win32-x64/win32-console-mode.node'],
});

const fsNativeExtensionsFileByTarget = Object.freeze(
Object.fromEntries(
SUPPORTED_TARGETS.map((target) => [
target,
[`prebuilds/${target}/fs-native-extensions.node`],
]),
),
);

export function isSupportedTarget(target) {
return SUPPORTED_TARGETS.includes(target);
}
Expand Down Expand Up @@ -84,6 +93,13 @@ export const nativeDeps = Object.freeze([
parent: null,
nativeFileRelatives: (target) => piTuiNativeFileByTarget[target] ?? [],
},
{
id: 'fs-native-extensions',
name: () => 'fs-native-extensions',
collect: 'native-file-only',
parent: null,
nativeFileRelatives: (target) => fsNativeExtensionsFileByTarget[target] ?? [],
},
]);

/**
Expand Down
3 changes: 2 additions & 1 deletion apps/kimi-code/src/cli/sub/web/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
DEFAULT_SERVER_HOST,
DEFAULT_SERVER_PORT,
parseServerOptions,
serverOrigin,
tryResolveServerToken,
VALID_LOG_LEVELS,
type ParsedServerOptions,
Expand Down Expand Up @@ -288,7 +289,7 @@ async function runServerInProcess(
});
logger.info('serving the REST/WS API and the bundled web UI');
running = {
address: `http://${v2.host}:${v2.port}`,
address: serverOrigin(v2.host, v2.port),
logger,
close: () => v2.close(),
};
Expand Down
9 changes: 3 additions & 6 deletions apps/kimi-code/src/cli/sub/web/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

import type { ServerLogLevel } from '@moonshot-ai/kap-server';
import { formatServerOrigin, type ServerLogLevel } from '@moonshot-ai/kap-server';

export const LOCAL_SERVER_HOST = '127.0.0.1';
export const DEFAULT_LAN_HOST = '0.0.0.0';
export const DEFAULT_SERVER_HOST = LOCAL_SERVER_HOST;
export const DEFAULT_SERVER_PORT = 58627;
export const DEFAULT_SERVER_ORIGIN = serverOrigin(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
export const DEFAULT_SERVER_ORIGIN = formatServerOrigin(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
export { formatServerOrigin as serverOrigin };

/** Filename (under KIMI_CODE_HOME) of the persistent server bearer token. */
export const SERVER_TOKEN_FILE = 'server.token';
Expand Down Expand Up @@ -112,10 +113,6 @@ export function parseLogLevel(raw: string | undefined): ServerLogLevel {
);
}

export function serverOrigin(host: string, port: number): string {
return `http://${host}:${port}`;
}

/** Strip `/api/v1` and trailing slashes so user-supplied origins are uniform. */
export function normalizeServerOrigin(value: string): string {
const url = new URL(value);
Expand Down
2 changes: 2 additions & 0 deletions apps/kimi-code/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { runUpdatePreflight } from './cli/update/preflight';
import { createKimiCodeHostIdentity, getVersion } from './cli/version';
import { CLI_SHUTDOWN_TIMEOUT_MS, CLI_UI_MODE, PROCESS_NAME } from './constant/app';
import { cleanupStaleNativeCacheForCurrent } from './native/native-assets';
import { installKernelFileLockNativeBinding } from './native/kernel-file-lock';
import { installNativeModuleHook } from './native/module-hook';
import { runNativeAssetSmokeIfRequested } from './native/smoke';

Expand Down Expand Up @@ -142,6 +143,7 @@ export function main(): void {
// invalid proxy URL is reported and ignored rather than aborting startup.
installGlobalProxyDispatcher();
installNativeModuleHook();
installKernelFileLockNativeBinding();
if (runNativeAssetSmokeIfRequested()) return;

// Start the background cleanup of stale native cache. Fire-and-forget; must not block startup or throw.
Expand Down
44 changes: 44 additions & 0 deletions apps/kimi-code/src/native/kernel-file-lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { join } from 'node:path';

import {
setKernelFileLockBindingLoader,
type KernelFileLockBinding,
} from '@moonshot-ai/agent-core-v2';

import { loadNativePackageFile } from './native-require';

interface NativeFileLockBinding {
tryLock(fd: number, offset: number, length: number, exclusive: boolean): void;
unlock(fd: number, offset: number, length: number): void;
}

function errorCode(error: unknown): string | undefined {
return (error as NodeJS.ErrnoException | undefined)?.code;
}

export function loadKernelFileLockNativeBinding(): KernelFileLockBinding | undefined {
const target = `${process.platform}-${process.arch}`;
const binding = loadNativePackageFile<NativeFileLockBinding>(
'fs-native-extensions',
join('prebuilds', target, 'fs-native-extensions.node'),
);
if (binding === null) return undefined;
return {
tryLock: (fd) => {
try {
binding.tryLock(fd, 0, 0, true);
return true;
} catch (error) {
if (errorCode(error) === 'EAGAIN') return false;
throw error;
}
},
unlock: (fd) => {
binding.unlock(fd, 0, 0);
},
};
}

export function installKernelFileLockNativeBinding(): void {
setKernelFileLockBindingLoader(loadKernelFileLockNativeBinding);
}
10 changes: 10 additions & 0 deletions apps/kimi-code/src/native/native-require.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,13 @@ export function loadNativePackage<T>(
if (nativeRequire === null) return null;
return nativeRequire(packageName) as T;
}

export function loadNativePackageFile<T>(
packageName: string,
relativePath: string,
options: NativeAssetOptions = {},
): T | null {
const packageRoot = getNativePackageRoot(packageName, options);
if (packageRoot === null) return null;
return createRequire(import.meta.url)(join(packageRoot, relativePath)) as T;
}
11 changes: 10 additions & 1 deletion apps/kimi-code/src/native/smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';

import { getEmbeddedNativeAssetManifest, getNativePackageRoot } from './native-assets';
import { loadKernelFileLockNativeBinding } from './kernel-file-lock';

const smokePackages = ['@mariozechner/clipboard', '@moonshot-ai/pi-tui'];
const smokePackages = ['@mariozechner/clipboard', '@moonshot-ai/pi-tui', 'fs-native-extensions'];

// Verify pi-tui's native helper can actually be loaded through the module hook.
// pi-tui computes native helper paths from process.execPath and require()s them;
Expand Down Expand Up @@ -34,6 +35,13 @@ function smokePiTuiNativeLoad(): void {
}
}

function smokeKernelFileLockNativeLoad(): void {
const binding = loadKernelFileLockNativeBinding();
if (binding === undefined) {
throw new Error('fs-native-extensions binding is unavailable.');
}
}

export function runNativeAssetSmokeIfRequested(): boolean {
if (process.env['KIMI_CODE_NATIVE_ASSET_SMOKE'] !== '1') return false;

Expand All @@ -49,6 +57,7 @@ export function runNativeAssetSmokeIfRequested(): boolean {
}
}
smokePiTuiNativeLoad();
smokeKernelFileLockNativeLoad();
process.stdout.write(`Native asset smoke passed: ${manifest.target}\n`);
process.exit(0);
} catch (error) {
Expand Down
11 changes: 10 additions & 1 deletion apps/kimi-code/test/cli/web/web.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { registerWebCommand } from '#/cli/sub/web';
import type { LegacyKillDeps } from '#/cli/sub/web/legacy-kill';
import type { WebCommandDeps } from '#/cli/sub/web/run';
import type { ParsedServerOptions } from '#/cli/sub/web/shared';
import { serverOrigin, type ParsedServerOptions } from '#/cli/sub/web/shared';
import { darkColors } from '#/tui/theme/colors';

vi.mock('node:child_process', async (importOriginal) => {
Expand Down Expand Up @@ -952,6 +952,15 @@ describe('formatHostForUrl', () => {
});
});

describe('serverOrigin', () => {
it('bracket-wraps an IPv6 host so the origin remains parseable', () => {
const origin = serverOrigin('::1', 58627);

expect(origin).toBe('http://[::1]:58627');
expect(new URL(origin).port).toBe('58627');
});
});

describe('filterDisplayAddresses', () => {
it('drops IPv6 link-local, de-duplicates, and orders IPv4 before IPv6', async () => {
const { filterDisplayAddresses } = await import('#/cli/sub/web/networks');
Expand Down
28 changes: 27 additions & 1 deletion apps/kimi-code/test/native/native-assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
type NativeAssetManifest,
type NativeAssetSource,
} from '#/native/native-assets';
import { loadNativePackage } from '#/native/native-require';
import { loadNativePackage, loadNativePackageFile } from '#/native/native-require';

function sha256(bytes: Buffer | string): string {
return createHash('sha256').update(bytes).digest('hex');
Expand Down Expand Up @@ -125,4 +125,30 @@ describe('native assets', () => {
rmSync(dir, { recursive: true, force: true });
}
});

it('loads a package file from extracted native assets', () => {
const dir = mkdtempSync(join(tmpdir(), 'kimi-native-file-require-'));
try {
const { manifest, source } = fakeManifest({
'node_modules/fake-native/package.json': '{}',
'node_modules/fake-native/prebuilds/test-target/addon.cjs':
"module.exports = { value: 'native' };\n",
});

const addon = loadNativePackageFile<{ value: string }>(
'fake-native',
join('prebuilds', 'test-target', 'addon.cjs'),
{
cacheBase: dir,
manifest,
source,
version: 'test',
},
);

expect(addon).toEqual({ value: 'native' });
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
});
16 changes: 16 additions & 0 deletions apps/kimi-code/test/scripts/native/native-deps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ describe('resolveTargetDeps', () => {
expect(names).toContain('@mariozechner/clipboard');
expect(names).toContain('@mariozechner/clipboard-darwin-arm64');
expect(names).toContain('@moonshot-ai/pi-tui');
expect(names).toContain('fs-native-extensions');
});

it('picks the right clipboard subpackage per target', () => {
Expand Down Expand Up @@ -75,6 +76,15 @@ describe('resolveTargetDeps', () => {
]);
});

it('encodes fs-native-extensions prebuild path per target', () => {
const fsNative = resolveTargetDeps('linux-x64').find(
(d) => d.resolvedName === 'fs-native-extensions',
);
expect(fsNative?.nativeFileRelatives).toEqual([
'prebuilds/linux-x64/fs-native-extensions.node',
]);
});

it('throws on unsupported target', () => {
expect(() => resolveTargetDeps('linux-x64-musl')).toThrow(/unsupported/i);
});
Expand All @@ -97,4 +107,10 @@ describe('nativeDeps registry shape', () => {
expect(piTui?.collect).toBe('native-file-only');
expect(piTui?.parent).toBe(null);
});

it('has fs-native-extensions (collect=native-file-only, no parent)', () => {
const fsNative = nativeDeps.find((d) => d.id === 'fs-native-extensions');
expect(fsNative?.collect).toBe('native-file-only');
expect(fsNative?.parent).toBe(null);
});
});
2 changes: 1 addition & 1 deletion apps/kimi-code/tsdown.native.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const builtins = new Set([
...builtinModules,
...builtinModules.map((name) => `node:${name}`),
]);
const optionalNativeDependencies = new Set(['cpu-features']);
const optionalNativeDependencies = new Set(['cpu-features', 'fs-native-extensions']);

function shouldAlwaysBundle(id: string): boolean {
if (builtins.has(id) || id.startsWith('node:')) return false;
Expand Down
12 changes: 10 additions & 2 deletions apps/kimi-web/src/api/daemon/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1463,8 +1463,16 @@ export class DaemonKimiWebApi implements KimiWebApi {
handlers.onConnectionChange(connected);
},

onError: (code: number, msg: string, fatal: boolean) => {
handlers.onError(code, msg, fatal);
onError: (code: number, msg: string, fatal: boolean, details?: unknown) => {
handlers.onError(code, msg, fatal, details);
},

onSessionListChanged: () => {
handlers.onSessionListChanged?.();
},

onSkillCatalogChanged: (sessionId: string) => {
handlers.onSkillCatalogChanged?.(sessionId);
},

onTerminalOutput: (sessionId, terminalId, data, seq) => {
Expand Down
Loading
Loading