Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kimi-code for openSUSE:Factory checked in at 2026-10-31 23:09:24 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kimi-code (Old) and /work/SRC/openSUSE:Factory/.kimi-code.new.1255 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kimi-code" Sat Oct 31 23:09:24 2026 rev:15 rq:1394172 version:2.4.1 Changes: -------- --- /work/SRC/openSUSE:Factory/kimi-code/kimi-code.changes 2026-10-27 16:59:35.274179828 +0100 +++ /work/SRC/openSUSE:Factory/.kimi-code.new.1255/kimi-code.changes 2026-10-31 23:11:49.665515811 +0100 @@ -1,0 +2,38 @@ +Sat Oct 31 15:27:17 UTC 2026 - Martin Pluskal <[email protected]> + +- Build the Linux NAPI clipboard helper from source instead of + shipping upstream's prebuilt binaries: + * Repack the npm tarball without package/native (6 prebuilt .node + files for darwin/linux/win32); the darwin/win32 legs never load + on Linux + * Compile the helper (one C11 file from the unpublished pi-tui + workspace package, vendored from the upstream release tag) + against the system libxcb in %build, and install it where the + inlined loader looks for the Linux prebuild + +------------------------------------------------------------------- +Sat Oct 31 13:38:38 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to version 2.4.1: + * Cap a single agent file read at 100,000 characters + (~25,000 tokens); raise both limits with max_chars + under [read] in config.toml + * Shorten built-in tool and subagent prompts and the + repeated goal and plan mode reminders + * Web dashboard interaction improvements and bug fixes + +------------------------------------------------------------------- +Thu Oct 29 12:21:39 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to version 2.4.0: + * Expand streaming thinking with Ctrl-O; import Claude Code + and Codex sessions with /import-sessions + * Plan mode is now opt-in with per-plan review; idle + sessions unload after 30 minutes and resume transparently + * Terminal status reporting, /provider refresh, higher + Tower concurrency, MCP reconnect and compaction fixes + * Drop the node-pty backend removed upstream (session + terminal service): node-pty-1.1.0.tgz and + node-addon-api-7.1.1.tgz go away with the native build + +------------------------------------------------------------------- Old: ---- kimi-code-2.3.3.tgz kimi-code-rpmlintrc node-addon-api-7.1.1.tgz node-pty-1.1.0.tgz New: ---- clipboard-worker.h clipboard.h kimi-code-2.4.1.tgz linux-platform-x11.c napi.h ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kimi-code.spec ++++++ --- /var/tmp/diff_new_pack.oGZvBo/_old 2026-10-31 23:11:50.767561564 +0100 +++ /var/tmp/diff_new_pack.oGZvBo/_new 2026-10-31 23:11:50.768561605 +0100 @@ -21,42 +21,53 @@ # runtime is vendored below. %global __nodejs_provides %{nil} %global __nodejs_requires %{nil} -%define node_pty_version 1.1.0 -%define node_addon_api_version 7.1.1 %define ws_version 8.21.3 %define qrcode_version 1.5.4 %define pngjs_version 5.0.0 %define dijkstrajs_version 1.0.3 %define chokidar_version 4.0.3 %define readdirp_version 4.1.2 +# Commit of the @moonshot-ai/[email protected] release tag the NAPI helper +# sources below are vendored from (tag object 1fc1d165c83b175873a46897e1e9db73dc70c984). +%define pi_tui_git 2c73f3f50af4fa7f2540ca0d2e33b07a1408e3a7 Name: kimi-code -Version: 2.3.3 +Version: 2.4.1 Release: 0 Summary: Command-line agentic coding assistant powered by Kimi models License: MIT URL: https://github.com/MoonshotAI/kimi-code # The published npm artifact is a prebuilt bundle (dist/main.mjs). Since 0.39.0 # it no longer inlines ws and qrcode, so those - plus qrcode's own runtime -# dependencies - are vendored alongside the optional native node-pty backend. -# Since 2.3.0 the bundle also imports chokidar externally (file watching), -# so it and its own dependency readdirp are vendored too. -Source0: https://registry.npmjs.org/@moonshot-ai/%{name}/-/%{name}-%{version}.tgz -Source1: https://registry.npmjs.org/node-pty/-/node-pty-%{node_pty_version}.tgz -Source2: https://registry.npmjs.org/node-addon-api/-/node-addon-api-%{node_addon_api_version}.tgz -Source3: https://registry.npmjs.org/ws/-/ws-%{ws_version}.tgz -Source4: https://registry.npmjs.org/qrcode/-/qrcode-%{qrcode_version}.tgz -Source5: https://registry.npmjs.org/pngjs/-/pngjs-%{pngjs_version}.tgz -Source6: https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-%{dijkstrajs_version}.tgz -Source7: https://registry.npmjs.org/chokidar/-/chokidar-%{chokidar_version}.tgz -Source8: https://registry.npmjs.org/readdirp/-/readdirp-%{readdirp_version}.tgz -Source99: kimi-code-rpmlintrc +# dependencies - are vendored below. Since 2.3.0 the bundle also imports +# chokidar externally (file watching), so it and its own dependency readdirp +# are vendored too. The node-pty backend went away upstream in 2.4.0 +# (session terminal service removed). The NAPI clipboard helper upstream only +# ships as prebuilt .node files is compiled from source in %build instead. +# Repacked npm tarball: package/native (6 prebuilt .node files for +# darwin/linux/win32 on x64+arm64) is deleted; the darwin/win32 legs never +# load on Linux and only the Linux leg is rebuilt. +# Recipe: gunzip the registry tarball, tar --delete -f it package/native, +# gzip -9 it again. +# https://registry.npmjs.org/@moonshot-ai/kimi-code/-/kimi-code-2.4.1.tgz +Source0: kimi-code-%{version}.tgz +Source1: https://registry.npmjs.org/ws/-/ws-%{ws_version}.tgz +Source2: https://registry.npmjs.org/qrcode/-/qrcode-%{qrcode_version}.tgz +Source3: https://registry.npmjs.org/pngjs/-/pngjs-%{pngjs_version}.tgz +Source4: https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-%{dijkstrajs_version}.tgz +Source5: https://registry.npmjs.org/chokidar/-/chokidar-%{chokidar_version}.tgz +Source6: https://registry.npmjs.org/readdirp/-/readdirp-%{readdirp_version}.tgz +# Linux NAPI helper sources from the private @moonshot-ai/pi-tui workspace +# package (never published to the registry), pinned to the release tag above. +Source10: https://raw.githubusercontent.com/MoonshotAI/kimi-code/%{pi_tui_git}/packages/pi-tui/native/napi.h +Source11: https://raw.githubusercontent.com/MoonshotAI/kimi-code/%{pi_tui_git}/packages/pi-tui/native/clipboard.h +Source12: https://raw.githubusercontent.com/MoonshotAI/kimi-code/%{pi_tui_git}/packages/pi-tui/native/linux/src/linux-platform-x11.c +Source13: https://raw.githubusercontent.com/MoonshotAI/kimi-code/%{pi_tui_git}/packages/pi-tui/native/linux/src/clipboard-worker.h BuildRequires: fdupes -BuildRequires: gcc-c++ -BuildRequires: make -BuildRequires: nodejs-devel >= 22.19 +BuildRequires: gcc +BuildRequires: nodejs >= 22.19 BuildRequires: nodejs-packaging -BuildRequires: npm -BuildRequires: python3 +BuildRequires: pkgconfig +BuildRequires: pkgconfig(xcb) Requires: fd Requires: nodejs >= 22.19 Requires: ripgrep @@ -65,14 +76,12 @@ # cannot be enumerated from the published artifact. Provides: bundled(chokidar) = %{chokidar_version} Provides: bundled(dijkstrajs) = %{dijkstrajs_version} -Provides: bundled(node-addon-api) = %{node_addon_api_version} -Provides: bundled(node-pty) = %{node_pty_version} Provides: bundled(pngjs) = %{pngjs_version} Provides: bundled(qrcode) = %{qrcode_version} Provides: bundled(readdirp) = %{readdirp_version} Provides: bundled(ws) = %{ws_version} -# node-pty compiles a native addon; the pure-JS bundle is otherwise portable, but -# only these arches are verified/relevant for the coding-agent workload. +# Only the Linux arches are built: the NAPI helper is compiled from source in +# %build, and the darwin/win32 prebuilds never load on Linux. ExclusiveArch: x86_64 aarch64 %description @@ -80,8 +89,7 @@ assistant for software-engineering tasks, driven by the Kimi family of models. It bundles a prebuilt web dashboard and shells out to ripgrep and fd for fast -code search. The interactive shell backend is provided by a compiled node-pty -addon. +code search. %prep %setup -q -c -T @@ -94,33 +102,30 @@ mv package "$2" } unpack_npm %{SOURCE0} kimi-code -unpack_npm %{SOURCE1} node-pty -unpack_npm %{SOURCE2} node-addon-api -unpack_npm %{SOURCE3} ws -unpack_npm %{SOURCE4} qrcode -unpack_npm %{SOURCE5} pngjs -unpack_npm %{SOURCE6} dijkstrajs -unpack_npm %{SOURCE7} chokidar -unpack_npm %{SOURCE8} readdirp +unpack_npm %{SOURCE1} ws +unpack_npm %{SOURCE2} qrcode +unpack_npm %{SOURCE3} pngjs +unpack_npm %{SOURCE4} dijkstrajs +unpack_npm %{SOURCE5} chokidar +unpack_npm %{SOURCE6} readdirp chmod -R a+rX,u+w,go-w . %build -# Compile the node-pty native addon fully offline. -# node-pty's binding.gyp resolves node-addon-api via require(), so place it -# where node-pty's own module resolution will find it. -mkdir -p node-pty/node_modules -cp -a node-addon-api node-pty/node_modules/node-addon-api -# node-gyp ships bundled inside npm; locate it rather than assuming it is on PATH. -node_gyp=$(find %{_prefix}/lib %{_libdir} -type f \ - -path '*node-gyp/bin/node-gyp.js' 2>/dev/null | head -n1) -pushd node-pty -export npm_config_nodedir=%{_prefix} -export npm_config_build_from_source=true -# scripts/prebuild.js only checks local files (no network); with -# build_from_source set it deletes the shipped darwin/win32 prebuilds and exits -# non-zero, so build the Linux addon (pty.node + spawn-helper) from source here. -node "$node_gyp" rebuild --nodedir=%{_prefix} -popd +# The Linux NAPI clipboard helper, compiled from source. Upstream ships it +# only as prebuilt .node files (packages/pi-tui/native in the unpublished +# pi-tui workspace package); the Linux helper is a single C11 file using +# only libxcb, with the small N-API surface node provides declared in the +# vendored napi.h. Flags mirror upstream's native/linux/build.sh. +mkdir -p pi-tui-native/native/linux/src +cp %{SOURCE10} pi-tui-native/native/napi.h +cp %{SOURCE11} pi-tui-native/native/clipboard.h +cp %{SOURCE12} pi-tui-native/native/linux/src/linux-platform-x11.c +cp %{SOURCE13} pi-tui-native/native/linux/src/clipboard-worker.h +cc %{optflags} -std=c11 -D_POSIX_C_SOURCE=200809L -Wall -Wextra -Werror \ + -fPIC -pthread -fvisibility=hidden -fno-stack-protector -shared -nostdlib \ + -Wl,--unresolved-symbols=ignore-all -Wl,--no-as-needed -Wl,--gc-sections \ + -Wl,-z,nodelete pi-tui-native/native/linux/src/linux-platform-x11.c \ + -lxcb -o linux-platform-x11.node %install # The bundle is installed by hand rather than with `npm install`: since 0.39.0 @@ -145,15 +150,6 @@ find "$kimiroot/node_modules" -type f -name '*.js' -exec chmod 0644 {} + find "$kimiroot/node_modules" -type f -name '*.js' -exec sed -i 's/\r$//' {} + -# Inject the freshly compiled node-pty so `import("node-pty")` resolves. -install -d "$kimiroot/node_modules/node-pty/build/Release" -cp -a node-pty/lib "$kimiroot/node_modules/node-pty/lib" -cp -a node-pty/package.json "$kimiroot/node_modules/node-pty/package.json" -# Only pty.node is needed on Linux; spawn-helper is a macOS-only path in -# node-pty's binding.gyp and pty.cc (the Linux fork path uses forkpty(3) and -# never execs the helper), so it is neither built nor referenced at runtime here. -cp -a node-pty/build/Release/pty.node "$kimiroot/node_modules/node-pty/build/Release/" - # Use a concrete node interpreter (openSUSE convention) rather than /usr/bin/env. sed -i '1s|^#!%{_bindir}/env node|#!%{_bindir}/node|' "$kimiroot/dist/main.mjs" chmod 0755 "$kimiroot/dist/main.mjs" @@ -164,9 +160,15 @@ # shim at npm-install time and are unused by the packaged CLI. rm -rf "$kimiroot/scripts" -# Strip non-Linux prebuilt addons shipped in the bundle (darwin/win32 only). -find %{buildroot}%{nodejs_sitelib} -type d -name 'darwin-*' -prune -exec rm -rf {} + -find %{buildroot}%{nodejs_sitelib} -type d -name 'win32-*' -prune -exec rm -rf {} + +# Install the from-source NAPI helper where the inlined pi-tui loader looks +# for it: <kimiroot>/native/<platform>/prebuilds/<platform>-<arch>/. Only the +# Linux leg is built (ExclusiveArch); darwin/win32 never load on Linux and +# the repacked tarball ships no prebuilds at all. +case "%{_target_cpu}" in + x86_64) nativearch="x64" ;; + aarch64) nativearch="arm64" ;; +esac +install -D -m 0755 linux-platform-x11.node "$kimiroot/native/linux/prebuilds/linux-$nativearch/linux-platform-x11.node" find %{buildroot}%{nodejs_sitelib} -type f \ \( -name '*.cmd' -o -name '*.bat' -o -name '*.ps1' -o -name '*.pdb' \) -delete @@ -176,9 +178,9 @@ # Starting the CLI evaluates dist/main.mjs, whose top-level imports of ws and # qrcode fail loudly if the vendored trees are missing or incomplete. %{buildroot}%{_bindir}/kimi --version -# node-pty is only reached through a dynamic import at runtime, so load the -# compiled addon explicitly to prove it works on this architecture. -node -e 'require("%{buildroot}%{nodejs_sitelib}/@moonshot-ai/%{name}/node_modules/node-pty")' +# The from-source NAPI helper must load and export the clipboard surface the +# smoke path (KIMI_CODE_NATIVE_ASSET_SMOKE=1) requires of the Linux prebuild. +node -e "const h = require('%{buildroot}%{nodejs_sitelib}/@moonshot-ai/kimi-code/native/linux/prebuilds/linux-' + (process.arch === 'arm64' ? 'arm64' : 'x64') + '/linux-platform-x11.node'); if (typeof h.getText !== 'function' || typeof h.getImage !== 'function') throw new Error('unexpected native helper exports')" %files %{_bindir}/kimi ++++++ clipboard-worker.h ++++++ #include <pthread.h> #include <time.h> #include "../../clipboard.h" static void read_clipboard(clipboard_job* job); static pthread_mutex_t worker_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_once_t worker_once = PTHREAD_ONCE_INIT; static pthread_cond_t worker_changed; static bool worker_ready, worker_busy, worker_waiting, worker_finished; static clipboard_job worker_result; static int (*signal_worker)(pthread_cond_t*); static int (*wait_for_worker)(pthread_cond_t*, pthread_mutex_t*, const struct timespec*); static void initialize_worker_condition(void) { // Unversioned ELF imports otherwise bind obsolete condition variables on glibc x64. int (*initialize)(pthread_cond_t*, const pthread_condattr_t*) = node_symbol("pthread_cond_init"); signal_worker = node_symbol("pthread_cond_signal"); wait_for_worker = node_symbol("pthread_cond_timedwait"); if (!initialize || !signal_worker || !wait_for_worker) return; pthread_condattr_t attributes; if (pthread_condattr_init(&attributes) != 0) return; worker_ready = pthread_condattr_setclock(&attributes, CLOCK_MONOTONIC) == 0 && initialize(&worker_changed, &attributes) == 0; pthread_condattr_destroy(&attributes); } static void* read_on_private_thread(void* unused) { (void)unused; read_clipboard(&worker_result); pthread_mutex_lock(&worker_mutex); worker_finished = true; if (!worker_waiting) { free(worker_result.data); // The caller timed out; discard the late result. worker_busy = false; } signal_worker(&worker_changed); pthread_mutex_unlock(&worker_mutex); return 0; } static void clipboard_execute(clipboard_job* job) { pthread_once(&worker_once, initialize_worker_condition); if (!worker_ready) return; pthread_mutex_lock(&worker_mutex); if (!worker_busy) { worker_busy = worker_waiting = true; worker_finished = false; worker_result = (clipboard_job){.operation = job->operation}; struct timespec deadline; clock_gettime(CLOCK_MONOTONIC, &deadline); deadline.tv_sec += 3; // Allow the two-second transfer deadline to report its own error first. pthread_t thread; if (pthread_create(&thread, 0, read_on_private_thread, 0) == 0) { pthread_detach(thread); // Only this bounded wait occupies libuv. Other callers return unavailable. while (!worker_finished && wait_for_worker(&worker_changed, &worker_mutex, &deadline) == 0) {} if (worker_finished) { job->data = worker_result.data; job->length = worker_result.length; job->format = worker_result.format; job->error = worker_result.error; worker_busy = false; } } else { worker_busy = false; } worker_waiting = false; } pthread_mutex_unlock(&worker_mutex); } ++++++ clipboard.h ++++++ #ifndef PI_CLIPBOARD_H #define PI_CLIPBOARD_H #include "napi.h" #ifndef _WIN32 #include <stdlib.h> #include <string.h> #endif typedef enum { CLIPBOARD_TEXT, CLIPBOARD_IMAGE, CLIPBOARD_WRITE, CLIPBOARD_FILES } clipboard_operation; // CLIPBOARD_PATHS stores concatenated, NUL-terminated UTF-8 paths in clipboard_job.data. typedef enum { CLIPBOARD_UNAVAILABLE, CLIPBOARD_EMPTY, CLIPBOARD_UTF8, CLIPBOARD_LATIN1, CLIPBOARD_UTF16, CLIPBOARD_BUFFER, CLIPBOARD_PATHS } clipboard_format; typedef struct { napi_async_work work; napi_deferred deferred; clipboard_operation operation; clipboard_format format; void* data; size_t length; const char* error; } clipboard_job; // Only this function runs on the worker thread. It must not call N-API. static void clipboard_execute(clipboard_job* job); static void* clipboard_alloc(size_t size) { #ifdef _WIN32 return GlobalAlloc(GPTR, size); #else return calloc(1, size); #endif } static void clipboard_free(void* data) { #ifdef _WIN32 if (data) GlobalFree(data); #else free(data); #endif } #ifdef PI_CLIPBOARD_WRITE static void clipboard_copy(clipboard_job* job, const void* data, size_t length, clipboard_format format) { job->data = clipboard_alloc(length ? length : 1); if (!job->data) { job->error = "Out of memory"; return; } #ifdef _WIN32 volatile uint8_t* target = job->data; const volatile uint8_t* source = data; for (size_t index = 0; index < length; index++) target[index] = source[index]; #else memcpy(job->data, data, length); #endif job->length = format == CLIPBOARD_UTF16 ? length / sizeof(uint16_t) : length; job->format = format; } #endif static void PI_NAPI_CALL execute_clipboard_work(napi_env env, void* data) { (void)env; clipboard_execute(data); } static int create_clipboard_paths_value( napi_env env, const clipboard_job* job, napi_create_string_utf8_fn create_string, napi_value* result ) { napi_create_array_fn create_array = (napi_create_array_fn)node_symbol("napi_create_array"); napi_set_element_fn set_element = (napi_set_element_fn)node_symbol("napi_set_element"); if (!create_array || !set_element || create_array(env, result) != 0) return 1; const char* cursor = job->data; const char* end = cursor + job->length; for (uint32_t index = 0; cursor < end; index++) { const char* terminator = cursor; while (terminator < end && *terminator != '\0') terminator++; if (terminator == end) return 1; napi_value item = 0; if (create_string(env, cursor, (size_t)(terminator - cursor), &item) != 0 || set_element(env, *result, index, item) != 0) { return 1; } cursor = terminator + 1; } return 0; } static void PI_NAPI_CALL complete_clipboard_work(napi_env env, int status, void* data) { clipboard_job* job = data; napi_create_string_utf8_fn create_string = (napi_create_string_utf8_fn)node_symbol("napi_create_string_utf8"); napi_value result = undefined_value(env); if (status != 0) job->error = "Clipboard operation cancelled"; if (!job->error && job->operation != CLIPBOARD_WRITE) { if (job->format == CLIPBOARD_EMPTY) { result = null_value(env); } else if (job->format == CLIPBOARD_PATHS) { status = create_clipboard_paths_value(env, job, create_string, &result); } else if (job->format == CLIPBOARD_BUFFER) { napi_create_buffer_copy_fn create_buffer = (napi_create_buffer_copy_fn)node_symbol("napi_create_buffer_copy"); status = create_buffer(env, job->length, job->data, 0, &result); } else if (job->format == CLIPBOARD_UTF16) { napi_create_string_utf16_fn create_utf16 = (napi_create_string_utf16_fn)node_symbol("napi_create_string_utf16"); status = create_utf16(env, job->data, job->length, &result); } else if (job->format == CLIPBOARD_UTF8 || job->format == CLIPBOARD_LATIN1) { napi_create_string_utf8_fn create_text = job->format == CLIPBOARD_LATIN1 ? (napi_create_string_utf8_fn)node_symbol("napi_create_string_latin1") : create_string; status = create_text(env, job->data, job->length, &result); } if (status != 0) job->error = "Could not create clipboard value"; } if (job->error) { napi_create_error_fn create_error = (napi_create_error_fn)node_symbol("napi_create_error"); napi_value message; create_string(env, job->error, NAPI_AUTO_LENGTH, &message); create_error(env, 0, message, &result); } napi_settle_deferred_fn settle = (napi_settle_deferred_fn)node_symbol( job->error ? "napi_reject_deferred" : "napi_resolve_deferred" ); settle(env, job->deferred, result); napi_async_work_fn delete_work = (napi_async_work_fn)node_symbol("napi_delete_async_work"); delete_work(env, job->work); clipboard_free(job->data); clipboard_free(job); } static napi_value queue_clipboard(napi_env env, napi_callback_info info, clipboard_operation operation) { napi_create_async_work_fn create_work = (napi_create_async_work_fn)node_symbol("napi_create_async_work"); napi_async_work_fn queue_work = (napi_async_work_fn)node_symbol("napi_queue_async_work"); napi_async_work_fn delete_work = (napi_async_work_fn)node_symbol("napi_delete_async_work"); napi_create_promise_fn create_promise = (napi_create_promise_fn)node_symbol("napi_create_promise"); napi_create_string_utf8_fn create_string = (napi_create_string_utf8_fn)node_symbol("napi_create_string_utf8"); if (!create_work || !queue_work || !delete_work || !create_promise || !create_string) { return fail(env, "Async clipboard API unavailable"); } clipboard_job* job = clipboard_alloc(sizeof(*job)); if (!job) return fail(env, "Out of memory"); job->operation = operation; #ifdef PI_CLIPBOARD_WRITE if (operation == CLIPBOARD_WRITE) { napi_get_cb_info_fn get_info = (napi_get_cb_info_fn)node_symbol("napi_get_cb_info"); #ifdef _WIN32 napi_get_value_string_utf16_fn get_string = (napi_get_value_string_utf16_fn)node_symbol("napi_get_value_string_utf16"); size_t unit_size = sizeof(uint16_t); #else napi_get_value_string_utf8_fn get_string = (napi_get_value_string_utf8_fn)node_symbol("napi_get_value_string_utf8"); size_t unit_size = 1; #endif size_t argc = 1; napi_value argument = 0; if (!get_info || !get_string || get_info(env, info, &argc, &argument, 0, 0) != 0 || argc == 0 || get_string(env, argument, 0, 0, &job->length) != 0) { clipboard_free(job); return fail(env, "setText requires a string"); } job->data = job->length < SIZE_MAX / unit_size ? clipboard_alloc((job->length + 1) * unit_size) : 0; if (!job->data || get_string(env, argument, job->data, job->length + 1, &job->length) != 0) { clipboard_free(job->data); clipboard_free(job); return fail(env, "Could not read clipboard text"); } } #else (void)info; #endif napi_value name = 0; napi_value promise = 0; if (create_string(env, "pi.clipboard", NAPI_AUTO_LENGTH, &name) != 0 || create_work(env, 0, name, execute_clipboard_work, complete_clipboard_work, job, &job->work) != 0 || create_promise(env, &job->deferred, &promise) != 0 || queue_work(env, job->work) != 0) { if (job->work) delete_work(env, job->work); clipboard_free(job->data); clipboard_free(job); return fail(env, "Could not queue clipboard operation"); } return promise; } static napi_value PI_NAPI_CALL get_clipboard_text(napi_env env, napi_callback_info info) { return queue_clipboard(env, info, CLIPBOARD_TEXT); } static napi_value PI_NAPI_CALL get_clipboard_image(napi_env env, napi_callback_info info) { return queue_clipboard(env, info, CLIPBOARD_IMAGE); } #ifdef PI_CLIPBOARD_WRITE static napi_value PI_NAPI_CALL set_clipboard_text(napi_env env, napi_callback_info info) { return queue_clipboard(env, info, CLIPBOARD_WRITE); } #endif #endif ++++++ kimi-code-2.3.3.tgz -> kimi-code-2.4.1.tgz ++++++ /work/SRC/openSUSE:Factory/kimi-code/kimi-code-2.3.3.tgz /work/SRC/openSUSE:Factory/.kimi-code.new.1255/kimi-code-2.4.1.tgz differ: char 4, line 1 ++++++ linux-platform-x11.c ++++++ #include <xcb/xcb.h> #include <string.h> #include <xcb/xcbext.h> #include <errno.h> #include <poll.h> #include <time.h> #include "clipboard-worker.h" #define MAX_CLIPBOARD_BYTES (50u * 1024u * 1024u) #define CLIPBOARD_TIMEOUT_MS 2000 static const char* const text_types[] = { "text/plain;charset=utf-8", "text/plain;charset=UTF-8", "UTF8_STRING", "text/plain", "STRING", }; static const char* const image_types[] = { "image/png", "image/jpeg", "image/webp", "image/gif", "image/bmp", "image/tiff", }; static int64_t monotonic_ms(void) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; } typedef struct { unsigned char* data; size_t length; uint32_t items; uint8_t format; xcb_atom_t type; } property_data; typedef struct { xcb_connection_t* connection; xcb_window_t window; xcb_atom_t clipboard; xcb_atom_t property; xcb_atom_t targets; xcb_atom_t incr; int64_t deadline; } x11_clipboard; static bool append_bytes(property_data* result, const unsigned char* bytes, size_t length) { if (length > MAX_CLIPBOARD_BYTES - result->length) return false; unsigned char* data = realloc(result->data, result->length + length + 1); if (!data) return false; memcpy(data + result->length, bytes, length); result->length += length; data[result->length] = 0; result->data = data; return true; } // Share one deadline across discovery, replies, and incremental chunks. // Connection setup and flushing are libxcb calls on the private thread. static bool wait_for_input(x11_clipboard* clipboard) { for (;;) { int64_t remaining = clipboard->deadline - monotonic_ms(); if (remaining <= 0 || xcb_connection_has_error(clipboard->connection)) return false; struct pollfd descriptor = {xcb_get_file_descriptor(clipboard->connection), POLLIN, 0}; int ready = poll(&descriptor, 1, (int)remaining); if (ready >= 0) return ready && (descriptor.revents & POLLIN); if (errno != EINTR) return false; } } static void* wait_for_reply(x11_clipboard* clipboard, unsigned sequence) { if (xcb_flush(clipboard->connection) <= 0) return 0; while (monotonic_ms() < clipboard->deadline) { void* reply = 0; xcb_generic_error_t* error = 0; if (xcb_poll_for_reply(clipboard->connection, sequence, &reply, &error)) { free(error); return reply; } if (!wait_for_input(clipboard)) break; } xcb_discard_reply(clipboard->connection, sequence); return 0; } static xcb_generic_event_t* wait_for_event(x11_clipboard* clipboard, uint8_t type) { if (xcb_flush(clipboard->connection) <= 0) return 0; while (monotonic_ms() < clipboard->deadline) { xcb_generic_event_t* event = xcb_poll_for_event(clipboard->connection); if (event) { if ((event->response_type & 0x7f) == type) return event; free(event); } else if (!wait_for_input(clipboard)) { break; } } return 0; } static bool read_property(x11_clipboard* clipboard, bool remove, property_data* result) { xcb_get_property_cookie_t cookie = xcb_get_property( clipboard->connection, remove, clipboard->window, clipboard->property, XCB_GET_PROPERTY_TYPE_ANY, 0, MAX_CLIPBOARD_BYTES / 4 ); xcb_get_property_reply_t* reply = wait_for_reply(clipboard, cookie.sequence); if (!reply) return false; bool copied = false; bool valid_format = reply->format == 8 || reply->format == 16 || reply->format == 32; uint64_t length = (uint64_t)reply->value_len * (reply->format / 8); if (reply->bytes_after == 0 && reply->type != XCB_NONE && valid_format && length <= MAX_CLIPBOARD_BYTES && length <= (uint64_t)reply->length * 4) { result->type = reply->type; result->format = reply->format; result->items = reply->value_len; copied = append_bytes(result, xcb_get_property_value(reply), (size_t)length); } free(reply); return copied; } static bool append_property(property_data* result, const property_data* chunk) { if ((chunk->format != 8 && chunk->format != 16 && chunk->format != 32) || chunk->type == XCB_NONE || (uint64_t)chunk->items * (chunk->format / 8) != chunk->length || (result->type != XCB_NONE && (result->type != chunk->type || result->format != chunk->format)) || chunk->items > UINT32_MAX - result->items) return false; if (!append_bytes(result, chunk->data, chunk->length)) return false; result->type = chunk->type; result->format = chunk->format; result->items += chunk->items; return true; } static bool request_selection(x11_clipboard* clipboard, xcb_atom_t target, property_data* result) { memset(result, 0, sizeof(*result)); xcb_delete_property(clipboard->connection, clipboard->window, clipboard->property); xcb_convert_selection( clipboard->connection, clipboard->window, clipboard->clipboard, target, clipboard->property, XCB_CURRENT_TIME ); while (true) { xcb_selection_notify_event_t* event = (xcb_selection_notify_event_t*)wait_for_event(clipboard, XCB_SELECTION_NOTIFY); if (!event) return false; bool matches = event->selection == clipboard->clipboard && event->target == target; bool received = event->property != XCB_NONE; free(event); if (!matches) continue; if (!received) return true; // No owner, or the owner does not offer this target. if (!read_property(clipboard, false, result)) return false; break; } if (result->type != clipboard->incr) return true; free(result->data); memset(result, 0, sizeof(*result)); xcb_delete_property(clipboard->connection, clipboard->window, clipboard->property); while (true) { xcb_property_notify_event_t* event = (xcb_property_notify_event_t*)wait_for_event(clipboard, XCB_PROPERTY_NOTIFY); if (!event) return false; bool matches = event->atom == clipboard->property && event->state == XCB_PROPERTY_NEW_VALUE; free(event); if (!matches) continue; property_data chunk = {0}; if (!read_property(clipboard, true, &chunk)) return false; bool appended = append_property(result, &chunk); bool finished = chunk.items == 0; free(chunk.data); if (!appended) return false; if (finished) return true; } } static xcb_atom_t intern_atom(x11_clipboard* clipboard, const char* name) { xcb_intern_atom_cookie_t cookie = xcb_intern_atom(clipboard->connection, false, strlen(name), name); xcb_intern_atom_reply_t* reply = wait_for_reply(clipboard, cookie.sequence); if (!reply) return XCB_NONE; xcb_atom_t atom = reply->atom; free(reply); return atom; } static bool open_clipboard(x11_clipboard* clipboard) { memset(clipboard, 0, sizeof(*clipboard)); clipboard->deadline = monotonic_ms() + CLIPBOARD_TIMEOUT_MS; int screen_number = 0; clipboard->connection = xcb_connect(0, &screen_number); if (xcb_connection_has_error(clipboard->connection)) return false; xcb_screen_iterator_t screens = xcb_setup_roots_iterator(xcb_get_setup(clipboard->connection)); while (screen_number-- > 0 && screens.rem) xcb_screen_next(&screens); if (!screens.rem) return false; clipboard->window = xcb_generate_id(clipboard->connection); uint32_t mask = XCB_EVENT_MASK_PROPERTY_CHANGE; xcb_create_window( clipboard->connection, XCB_COPY_FROM_PARENT, clipboard->window, screens.data->root, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, &mask ); clipboard->clipboard = intern_atom(clipboard, "CLIPBOARD"); clipboard->property = intern_atom(clipboard, "PI_CLIPBOARD"); clipboard->targets = intern_atom(clipboard, "TARGETS"); clipboard->incr = intern_atom(clipboard, "INCR"); return clipboard->clipboard && clipboard->property && clipboard->targets && clipboard->incr; } static void close_clipboard(x11_clipboard* clipboard) { if (clipboard->connection) xcb_disconnect(clipboard->connection); } static bool preferred_target(x11_clipboard* clipboard, bool image, xcb_atom_t* target) { const char* const* types = image ? image_types : text_types; size_t type_count = image ? 6 : 5; xcb_atom_t wanted[6]; for (size_t index = 0; index < type_count; index++) { wanted[index] = intern_atom(clipboard, types[index]); if (wanted[index] == XCB_NONE) return false; } property_data targets = {0}; bool received = request_selection(clipboard, clipboard->targets, &targets); bool valid = targets.type == XCB_ATOM_ATOM && targets.format == 32 && targets.items == targets.length / sizeof(xcb_atom_t) && targets.length % sizeof(xcb_atom_t) == 0; if (received && valid) { xcb_atom_t* offered = (xcb_atom_t*)targets.data; for (size_t wanted_index = 0; wanted_index < type_count; wanted_index++) { for (uint32_t offered_index = 0; offered_index < targets.items; offered_index++) { if (offered[offered_index] == wanted[wanted_index]) { free(targets.data); *target = wanted[wanted_index]; return true; } } } } free(targets.data); // Some owners refuse TARGETS but still serve text directly. if (received && !image && targets.type == XCB_NONE) { *target = intern_atom(clipboard, "UTF8_STRING"); return *target != XCB_NONE; } return received && (valid || targets.type == XCB_NONE); } static bool read_selection(x11_clipboard* clipboard, bool image, property_data* result) { xcb_atom_t target = XCB_NONE; bool received = preferred_target(clipboard, image, &target) && (target == XCB_NONE || request_selection(clipboard, target, result)); if (!received) { free(result->data); memset(result, 0, sizeof(*result)); } return received; } static void read_clipboard(clipboard_job* job) { x11_clipboard clipboard; if (open_clipboard(&clipboard)) { property_data contents = {0}; if (!read_selection(&clipboard, job->operation == CLIPBOARD_IMAGE, &contents)) { job->error = "Could not read X11 clipboard"; } else { job->data = contents.data; job->length = contents.length; job->format = !contents.data ? CLIPBOARD_EMPTY : job->operation == CLIPBOARD_IMAGE ? CLIPBOARD_BUFFER : contents.type == XCB_ATOM_STRING ? CLIPBOARD_LATIN1 : CLIPBOARD_UTF8; } } close_clipboard(&clipboard); } PI_NAPI_EXPORT napi_value napi_register_module_v1(napi_env env, napi_value exports) { set_function_export(env, exports, "getText", get_clipboard_text); set_function_export(env, exports, "getImage", get_clipboard_image); return exports; } ++++++ napi.h ++++++ #ifndef PI_NAPI_H #define PI_NAPI_H // Resolve the small N-API surface we use from Node or Bun, without Node headers // or a link-time dependency on a particular runtime. #include <stdbool.h> #include <stddef.h> #include <stdint.h> #ifdef _WIN32 #include <windows.h> #define PI_NAPI_CALL __cdecl #define PI_NAPI_EXPORT __declspec(dllexport) #else #include <dlfcn.h> #define PI_NAPI_CALL #define PI_NAPI_EXPORT __attribute__((visibility("default"))) #endif #define NAPI_AUTO_LENGTH ((size_t)-1) typedef void* napi_env; typedef void* napi_value; typedef void* napi_callback_info; typedef void* napi_async_work; typedef void* napi_deferred; typedef void (PI_NAPI_CALL *napi_async_execute_callback)(napi_env, void*); typedef void (PI_NAPI_CALL *napi_async_complete_callback)(napi_env, int, void*); typedef int (PI_NAPI_CALL *napi_create_async_work_fn)(napi_env, napi_value, napi_value, napi_async_execute_callback, napi_async_complete_callback, void*, napi_async_work*); typedef int (PI_NAPI_CALL *napi_create_array_fn)(napi_env, napi_value*); typedef int (PI_NAPI_CALL *napi_async_work_fn)(napi_env, napi_async_work); typedef int (PI_NAPI_CALL *napi_create_promise_fn)(napi_env, napi_deferred*, napi_value*); typedef int (PI_NAPI_CALL *napi_settle_deferred_fn)(napi_env, napi_deferred, napi_value); typedef int (PI_NAPI_CALL *napi_create_error_fn)(napi_env, napi_value, napi_value, napi_value*); typedef napi_value (PI_NAPI_CALL *napi_callback)(napi_env, napi_callback_info); typedef int (PI_NAPI_CALL *napi_create_buffer_copy_fn)(napi_env, size_t, const void*, void**, napi_value*); typedef int (PI_NAPI_CALL *napi_create_function_fn)(napi_env, const char*, size_t, napi_callback, void*, napi_value*); typedef int (PI_NAPI_CALL *napi_create_string_utf8_fn)(napi_env, const char*, size_t, napi_value*); typedef int (PI_NAPI_CALL *napi_create_string_utf16_fn)(napi_env, const uint16_t*, size_t, napi_value*); typedef int (PI_NAPI_CALL *napi_get_boolean_fn)(napi_env, bool, napi_value*); typedef int (PI_NAPI_CALL *napi_get_cb_info_fn)(napi_env, napi_callback_info, size_t*, napi_value*, napi_value*, void**); typedef int (PI_NAPI_CALL *napi_get_value_fn)(napi_env, napi_value*); typedef int (PI_NAPI_CALL *napi_get_value_string_utf8_fn)(napi_env, napi_value, char*, size_t, size_t*); typedef int (PI_NAPI_CALL *napi_get_value_string_utf16_fn)(napi_env, napi_value, uint16_t*, size_t, size_t*); typedef int (PI_NAPI_CALL *napi_set_named_property_fn)(napi_env, napi_value, const char*, napi_value); typedef int (PI_NAPI_CALL *napi_set_element_fn)(napi_env, napi_value, uint32_t, napi_value); typedef int (PI_NAPI_CALL *napi_throw_error_fn)(napi_env, const char*, const char*); static void* node_symbol(const char* name) { #ifdef _WIN32 HMODULE module = GetModuleHandleA(0); void* proc = module ? (void*)GetProcAddress(module, name) : 0; if (proc) return proc; module = GetModuleHandleA("node.dll"); return module ? (void*)GetProcAddress(module, name) : 0; #else return dlsym(RTLD_DEFAULT, name); #endif } static napi_value undefined_value(napi_env env) { napi_get_value_fn get_value = (napi_get_value_fn)node_symbol("napi_get_undefined"); napi_value result = 0; if (get_value) get_value(env, &result); return result; } static napi_value null_value(napi_env env) { napi_get_value_fn get_value = (napi_get_value_fn)node_symbol("napi_get_null"); napi_value result = 0; if (get_value) get_value(env, &result); return result; } static napi_value fail(napi_env env, const char* message) { napi_throw_error_fn napi_throw_error = (napi_throw_error_fn)node_symbol("napi_throw_error"); if (napi_throw_error) napi_throw_error(env, 0, message); return undefined_value(env); } static void set_function_export(napi_env env, napi_value exports, const char* name, napi_callback callback) { napi_create_function_fn napi_create_function = (napi_create_function_fn)node_symbol("napi_create_function"); napi_set_named_property_fn napi_set_named_property = (napi_set_named_property_fn)node_symbol("napi_set_named_property"); napi_value fn = 0; if (napi_create_function && napi_set_named_property && napi_create_function(env, name, NAPI_AUTO_LENGTH, callback, 0, &fn) == 0) { napi_set_named_property(env, exports, name, fn); } } #endif
