Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
907bf0e
Auto-detect HF model cache to bypass Rust TLS in restricted environments
claude Jun 17, 2026
c1ca1bb
Merge pull request #1 from danielhertz1999-bit/claude/awesome-turing-…
danielhertz1999-bit Jun 17, 2026
1dc1d64
Add platform-agnostic IPC transport layer for Windows porting
danielhertz1999-bit Jun 18, 2026
8154b9b
Step 2: Replace fcntl file-locking with platform-agnostic shim
danielhertz1999-bit Jun 18, 2026
c009736
Steps 3+4+9: Guard resource, POSIX signals, and log paths for Windows
danielhertz1999-bit Jun 18, 2026
8ecd257
Steps 7+10: Guard os.geteuid, os.fchmod, and add icacls key security
danielhertz1999-bit Jun 18, 2026
0e8321c
Step 5: Windows Task Scheduler daemon installer + handoff doc
danielhertz1999-bit Jun 18, 2026
f4865bf
Step 6: Windows PowerShell hook equivalents + hook installer updates
danielhertz1999-bit Jun 18, 2026
0d41e0a
Update WINDOWS_PORT_HANDOFF.md: mark Steps 1-6 complete, add next ste…
danielhertz1999-bit Jun 18, 2026
59839a3
Step 7: Port bench RSS helpers off POSIX resource module
danielhertz1999-bit Jun 18, 2026
13808e1
Fix lifecycle_event_log import regression from Step 2
danielhertz1999-bit Jun 19, 2026
019e52f
Make `daemon` subcommand help text Windows-aware
danielhertz1999-bit Jun 19, 2026
1b2ca70
Update WINDOWS_PORT_HANDOFF.md with in-situ Windows verification results
danielhertz1999-bit Jun 19, 2026
269e90a
Fix test collection on Windows: swap fcntl/resource for cross-platfor…
danielhertz1999-bit Jun 20, 2026
33643f6
Fix capture-hooks install crash when MCP wrapper is unbuilt
danielhertz1999-bit Jun 20, 2026
a1d7b90
Use explicit UTF-8 for all text file I/O (fix cp1252 crash on Windows)
danielhertz1999-bit Jun 20, 2026
298193b
Add iai_mcp.__main__ and force UTF-8 std streams (unblock Windows hooks)
danielhertz1999-bit Jun 20, 2026
ecf07a6
_filelock: preserve file offset and emulate POSIX blocking on Windows
danielhertz1999-bit Jun 20, 2026
e5948b9
Use os.replace for atomic file moves (fix WinError 183 on Windows)
danielhertz1999-bit Jun 20, 2026
02d370f
Document the LOCK_SH-is-exclusive divergence on Windows
danielhertz1999-bit Jun 20, 2026
c3b2d84
Port test suite to run on Windows: /tmp paths, mode assertions, cwd s…
claude Jun 20, 2026
207eb6c
Update mcp-wrapper/package-lock.json
claude Jun 20, 2026
93e52c1
Merge remote-tracking branch 'upstream/main'
danielhertz1999-bit Jun 20, 2026
a20f77d
Merge remote-tracking branch 'origin/main'
danielhertz1999-bit Jun 20, 2026
e21a689
Add auth-token handshake to Windows TCP loopback IPC
claude Jun 20, 2026
7b13793
lifecycle_lock: skip os.kill(pid,0) liveness probe on Windows
danielhertz1999-bit Jun 20, 2026
9ced147
cli/_daemon: drop <WorkingDirectory> from schtasks XML
danielhertz1999-bit Jun 20, 2026
1edccb3
daemon_state: retry os.replace on Windows to survive reader contention
danielhertz1999-bit Jun 20, 2026
5d26920
tests/cli_daemon: port fake daemon + stop tests to Windows
danielhertz1999-bit Jun 20, 2026
f9f6e7e
docs: mark Windows port complete with E2E results
danielhertz1999-bit Jun 20, 2026
f186bb0
mcp-wrapper: port IPC transport to Windows (TCP loopback)
danielhertz1999-bit Jun 21, 2026
e99ba52
Merge pull request #3 from danielhertz1999-bit/windows-port-completion
danielhertz1999-bit Jun 21, 2026
c247a5e
Merge remote-tracking branch 'upstream/main' into HEAD
danielhertz1999-bit Jun 22, 2026
33ea96b
Fix Windows RGC worker hang: run rebuild in-process thread, not spawn
danielhertz1999-bit Jun 22, 2026
6f5ad80
Merge tag 'v1.1.5' into windows-port-completion
danielhertz1999-bit Jun 22, 2026
58162e6
Fix Windows RGC worker hang: run rebuild in-process thread, not spawn
danielhertz1999-bit Jun 22, 2026
86d5837
Merge pull request #4 from danielhertz1999-bit/windows-port-completion
danielhertz1999-bit Jun 22, 2026
fc9ace7
Merge pull request #5 from danielhertz1999-bit/fix/windows-rgc-worker…
danielhertz1999-bit Jun 22, 2026
d3fadd7
_ipc: honor IAI_DAEMON_SOCKET_PATH for the Windows port file
danielhertz1999-bit Jun 22, 2026
aead274
Merge pull request #6 from danielhertz1999-bit/windows-ipc-portfile-i…
danielhertz1999-bit Jun 22, 2026
8713a89
tests: port socket-dispatch suite to cross-platform _ipc transport
danielhertz1999-bit Jun 22, 2026
e21602e
tests: port socket-activity-tracking suite to _ipc + fix /tmp path
danielhertz1999-bit Jun 22, 2026
3bb1e35
tests: port daemon-dispatcher suite to _ipc (16 tests pass on Windows)
danielhertz1999-bit Jun 22, 2026
4d213d5
tests: port test_concurrency to _ipc; skip POSIX socket-file-mode tes…
danielhertz1999-bit Jun 22, 2026
ce69ed5
tests: port test_concurrency_session_open (threaded daemon) to _ipc (…
danielhertz1999-bit Jun 22, 2026
9cdd0fc
tests: add cross-platform bind_fake_daemon_socket helper; port iai_re…
danielhertz1999-bit Jun 22, 2026
bcf204a
tests: port lat05_asleep_skip + daemon_watchdog to _ipc; skip POSIX S…
danielhertz1999-bit Jun 22, 2026
cb0447a
doctor: fix check_b on Windows + port test_doctor_checklist
danielhertz1999-bit Jun 22, 2026
c5ecfd7
tests: skip POSIX unix-socket-routing hermeticity module on Windows (…
danielhertz1999-bit Jun 22, 2026
169f3a5
Merge pull request #7 from danielhertz1999-bit/windows-socket-tests-port
danielhertz1999-bit Jun 22, 2026
bf055a6
tests: port test_core_bedtime_inject (consent gate + threaded fake da…
danielhertz1999-bit Jun 22, 2026
0542895
tests: port test_socket_fail_loud (real daemon spawn) to _ipc; skip w…
danielhertz1999-bit Jun 22, 2026
55b6d4c
capture: fix _pid_is_alive on Windows + port test_daemon_crash_loop_i…
danielhertz1999-bit Jun 22, 2026
e023cb0
Merge pull request #8 from danielhertz1999-bit/windows-subprocess-tes…
danielhertz1999-bit Jun 23, 2026
912069b
Merge remote-tracking branch 'origin/main' into windows-tcp-auth-inte…
danielhertz1999-bit Jun 24, 2026
e8daa86
_ipc: isolate the Windows auth token per-endpoint + teach test helper…
danielhertz1999-bit Jun 24, 2026
8c46951
Merge pull request #2 from danielhertz1999-bit/windows-tcp-auth
danielhertz1999-bit Jun 24, 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
454 changes: 454 additions & 0 deletions WINDOWS_PORT_HANDOFF.md

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion bench/consolidation_rss_peak.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import gc
import json
import os
import resource
import shutil
import sys
import tempfile
Expand Down Expand Up @@ -38,6 +37,14 @@ def _cur_rss_bytes() -> int:


def _ru_maxrss_bytes() -> int:
if sys.platform == "win32":
try:
import psutil
mi = psutil.Process().memory_info()
return int(getattr(mi, "peak_wset", mi.rss))
except Exception:
return 0
import resource
r = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
if sys.platform == "darwin":
return int(r)
Expand Down
49 changes: 38 additions & 11 deletions bench/embed_warm_cost.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,42 @@
"""

_PAYLOAD_RSS = r"""
import sys, resource
import sys
sys.path.insert(0, {src_path!r})
import platform as _plat
_system = _plat.system()
if _system == "Windows":
import psutil as _psutil
def _peak_raw():
mi = _psutil.Process().memory_info()
return int(getattr(mi, "peak_wset", mi.rss))
def _to_mb(raw):
return raw / 1048576
_unit_is_bytes = True
else:
import resource as _resource
def _peak_raw():
return _resource.getrusage(_resource.RUSAGE_SELF).ru_maxrss
if _system == "Darwin":
def _to_mb(raw):
return raw / 1048576
_unit_is_bytes = True
else:
def _to_mb(raw):
return raw / 1024
_unit_is_bytes = False
from iai_mcp.embed import Embedder
e = Embedder()
rss_post_construct_raw = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
rss_post_construct_raw = _peak_raw()
text = {text!r}
_ = e.embed(text)
rss_post_encode_raw = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
import platform as _plat
is_mac = (_plat.system() == "Darwin")
def to_mb(raw):
return raw / 1048576 if is_mac else raw / 1024
print(f"rss_post_construct_mb={{to_mb(rss_post_construct_raw):.1f}}")
print(f"rss_post_encode_mb={{to_mb(rss_post_encode_raw):.1f}}")
rss_post_encode_raw = _peak_raw()
print(f"rss_post_construct_mb={{_to_mb(rss_post_construct_raw):.1f}}")
print(f"rss_post_encode_mb={{_to_mb(rss_post_encode_raw):.1f}}")
print(f"rss_post_construct_raw={{rss_post_construct_raw}}")
print(f"rss_post_encode_raw={{rss_post_encode_raw}}")
print(f"unit_is_bytes={{is_mac}}")
print(f"unit_is_bytes={{_unit_is_bytes}}")
print(f"rss_platform={{_system}}")
"""


Expand Down Expand Up @@ -210,17 +229,25 @@ def measure_rss(src_path: str, text: str) -> dict:
rss_post_construct_mb = float(kv["rss_post_construct_mb"])
rss_post_encode_mb = float(kv["rss_post_encode_mb"])
unit_is_bytes = kv["unit_is_bytes"] == "True"
rss_platform = kv.get("rss_platform", "")
if rss_platform == "Windows":
unit_label = "bytes (Windows peak_wset)"
elif rss_platform == "Darwin" or (unit_is_bytes and not rss_platform):
unit_label = "bytes (macOS)"
else:
unit_label = "KB (Linux)"
print(
f" RSS post-construct={rss_post_construct_mb:.1f}MB "
f"post-first-encode={rss_post_encode_mb:.1f}MB "
f"unit={'bytes (macOS)' if unit_is_bytes else 'KB (Linux)'}"
f"unit={unit_label}"
)
return {
"rss_post_construct_mb": rss_post_construct_mb,
"rss_post_encode_mb": rss_post_encode_mb,
"rss_post_construct_raw": int(kv["rss_post_construct_raw"]),
"rss_post_encode_raw": int(kv["rss_post_encode_raw"]),
"unit_is_bytes_macos": unit_is_bytes,
"rss_platform": rss_platform,
}


Expand Down
6 changes: 5 additions & 1 deletion bench/memory_footprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import gc
import json
import os
import resource
import sys
import tempfile
import time
Expand Down Expand Up @@ -42,6 +41,11 @@ def _threshold_mb_for_n(n: int) -> float:


def _rss_mb() -> float:
if sys.platform == "win32":
import psutil
mi = psutil.Process().memory_info()
return float(getattr(mi, "peak_wset", mi.rss)) / 1024.0 / 1024.0
import resource
r = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
if sys.platform == "darwin":
return float(r) / 1024.0 / 1024.0
Expand Down
6 changes: 5 additions & 1 deletion bench/memorygraph_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import argparse
import gc
import resource
import sys
from pathlib import Path
from uuid import uuid4
Expand All @@ -14,6 +13,11 @@


def rss_mb() -> float:
if sys.platform == "win32":
import psutil
mi = psutil.Process().memory_info()
return float(getattr(mi, "peak_wset", mi.rss)) / (1024 * 1024)
import resource
ru = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
if sys.platform == "darwin":
return ru / (1024 * 1024)
Expand Down
1 change: 1 addition & 0 deletions mcp-wrapper/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 26 additions & 24 deletions mcp-wrapper/src/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@

import * as crypto from "node:crypto";
import * as net from "node:net";
import * as os from "node:os";
import * as path from "node:path";
import {
type ConnectTarget,
createDaemonConnection,
daemonUnreachableHint,
getDaemonConnectTarget,
} from "./ipc.js";

function getDaemonSocketPath(): string {
return process.env.IAI_DAEMON_SOCKET_PATH
?? path.join(os.homedir(), ".iai-mcp", ".daemon.sock");
}
const SOCKET_CONNECT_TIMEOUT_MS = 5000;
const ERR_DAEMON_UNREACHABLE = -32002;

Expand Down Expand Up @@ -69,29 +69,30 @@ export class PythonCoreBridge {
private async _doStart(): Promise<void> {
this.reconnectAttempted = false;

const target = getDaemonConnectTarget();
if (target === null) {
throw new DaemonUnreachableError(daemonUnreachableHint());
}

let sock: net.Socket;
try {
sock = await this.connectWithTimeout(
getDaemonSocketPath(),
target,
SOCKET_CONNECT_TIMEOUT_MS,
);
} catch (e) {
throw new DaemonUnreachableError(
"iai-mcp daemon not running. "
+ "Run: launchctl load -w ~/Library/LaunchAgents/com.iai-mcp.daemon.plist "
+ "or run scripts/install.sh"
);
throw new DaemonUnreachableError(daemonUnreachableHint());
}
this.sock = sock;
this.attachSocketHandlers();
}

private connectWithTimeout(
socketPath: string,
target: ConnectTarget,
timeoutMs: number,
): Promise<net.Socket> {
return new Promise((resolve, reject) => {
const sock = net.createConnection(socketPath);
const sock = createDaemonConnection(target);
// Keep a pending/abandoned connect attempt from pinning the event loop
// (e.g. an in-flight reconnect after socket death). A live connected
// socket re-refs below so real RPC still holds the process open.
Expand Down Expand Up @@ -193,8 +194,12 @@ export class PythonCoreBridge {
if (testDelayMs > 0) {
await new Promise<void>((r) => setTimeout(r, testDelayMs));
}
const target = getDaemonConnectTarget();
if (target === null) {
return;
}
this.sock = await this.connectWithTimeout(
getDaemonSocketPath(),
target,
SOCKET_CONNECT_TIMEOUT_MS,
);
this.attachSocketHandlers();
Expand Down Expand Up @@ -254,13 +259,6 @@ export class PythonCoreBridge {
}


export function sessionOpenSocketPath(): string {
const env = process.env.IAI_DAEMON_SOCKET_PATH;
if (env) return env;
return path.join(os.homedir(), ".iai-mcp", ".daemon.sock");
}


export function newSessionId(): string {
return crypto.randomUUID();
}
Expand All @@ -275,8 +273,12 @@ export function emitSessionOpen(sessionId: string): Promise<void> {
resolve();
};
try {
const socketPath = sessionOpenSocketPath();
const sock = net.createConnection(socketPath, () => {
const target = getDaemonConnectTarget();
if (target === null) {
finish();
return;
}
const sock = createDaemonConnection(target, () => {
const msg =
JSON.stringify({
type: "session_open",
Expand Down
97 changes: 97 additions & 0 deletions mcp-wrapper/src/ipc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

/**
* Platform-agnostic IPC transport, mirroring the Python `iai_mcp._ipc` module.
*
* POSIX: Unix-domain socket -> ~/.iai-mcp/.daemon.sock
* Windows: TCP loopback -> 127.0.0.1:<port>, port read from
* ~/.iai-mcp/.daemon.port
*
* The base dir is ~/.iai-mcp (os.homedir()) to match `_ipc._BASE_DIR`, which
* uses Path.home() regardless of IAI_MCP_STORE.
*/
import * as fs from "node:fs";
import * as net from "node:net";
import * as os from "node:os";
import * as path from "node:path";

export const IS_WINDOWS = process.platform === "win32";

export type ConnectTarget = string | { host: string; port: number };

function daemonBaseDir(): string {
return path.join(os.homedir(), ".iai-mcp");
}

export function daemonSocketPath(): string {
return path.join(daemonBaseDir(), ".daemon.sock");
}

export function daemonPortFile(): string {
return path.join(daemonBaseDir(), ".daemon.port");
}

export function readDaemonPort(): number | null {
try {
const txt = fs.readFileSync(daemonPortFile(), "utf-8").trim();
const port = Number.parseInt(txt, 10);
return Number.isFinite(port) && port > 0 ? port : null;
} catch {
return null;
}
}

/**
* Resolve the daemon IPC endpoint.
* POSIX -> Unix-domain socket path (string)
* Windows -> { host: "127.0.0.1", port } from the port file
* Returns null when the endpoint cannot be determined (on Windows: port file
* absent => daemon not running). IAI_DAEMON_SOCKET_PATH overrides on POSIX.
*/
export function getDaemonConnectTarget(): ConnectTarget | null {
const env = process.env.IAI_DAEMON_SOCKET_PATH;
if (env) return env;
if (IS_WINDOWS) {
const port = readDaemonPort();
return port === null ? null : { host: "127.0.0.1", port };
}
return daemonSocketPath();
}

export function daemonUnreachableHint(): string {
if (IS_WINDOWS) {
return (
"iai-mcp daemon not running. "
+ 'Start it with: schtasks /Run /TN "iai-mcp-daemon" '
+ "(or: iai-mcp daemon install)."
);
}
if (process.platform === "darwin") {
return (
"iai-mcp daemon not running. "
+ "Run: launchctl load -w ~/Library/LaunchAgents/com.iai-mcp.daemon.plist "
+ "or run scripts/install.sh"
);
}
return (
"iai-mcp daemon not running. "
+ "Run: systemctl --user start iai-mcp-daemon or run scripts/install.sh"
);
}

/**
* Open a net.Socket to the daemon for either transport. Accepts the union
* target returned by getDaemonConnectTarget so callers stay platform-agnostic.
*/
export function createDaemonConnection(
target: ConnectTarget,
connectListener?: () => void,
): net.Socket {
if (typeof target === "string") {
return connectListener
? net.createConnection(target, connectListener)
: net.createConnection(target);
}
return connectListener
? net.createConnection(target.port, target.host, connectListener)
: net.createConnection(target.port, target.host);
}
Loading