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

Reply via email to