Crash and data-loss fixes. Each was reproduced before being fixed, and each carries a regression test verified to fail without it.
Fixed
intArgumentno longer crashes the server on an out-of-range number (#2). A JSON number outsideInt's range arrives as.double, andInt(_: Double)traps — so an unprivileged client could kill the process with one tool call. Now returnsnil, as documented, while keeping the documented truncating behaviour for in-range doubles.jsonResultno longer aborts on NaN or non-JSON values (#3).JSONSerializationraises an Objective-CNSException, whichtry?cannot catch, so the documented error path was unreachable. Guarded withisValidJSONObject(_:).JSONLLog.appendis now atomic (#4). The log openedO_WRONLYwithoutO_APPENDand did seek-then-write — a lost-update race. Measured before the fix: 4 concurrent writers × 500 appends lost 364 entries and tore 162 lines. Now opensO_WRONLY|O_APPEND|O_CREAT, so the kernel positions each write.- One invalid UTF-8 byte no longer empties the whole log (#5). Reading via
String(contentsOf:encoding:)failed for the entire file on a single bad byte — which the append race above produced — making the log read as empty andtrim()a permanent no-op. Lines are now split on0x0Aand decoded individually, delivering the tolerance the API already promised. - A failed open no longer destroys the log (#6). The fallback branch assumed "file doesn't exist", but also fired on
EACCES, and its atomic write renamed over the target — replacing the entire history. Verified before the fix:chmod 444plus one append reduced a 4-entry log to 1 entry. runOverStdioUntilExitno longer deadlocks a@MainActorprovider (#7). The semaphore blocked the main thread, which is the main actor's executor, so any provider touching main-actor state hung forever ontools/callwith no error. The main thread now goes todispatchMain(), keeping the main actor live.
Upgrading
intArgument returns nil instead of trapping for out-of-range, NaN, and infinite values, and "3.9" now coerces to 3 for consistency with 3.9. runOverStdioUntilExit no longer returns control by blocking; behaviour off the main thread is unchanged.
Full changelog: 0.1.0...0.2.0