@nanhara/hara 0.122.2 → 0.122.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/gateway/flows-pending.js +3 -3
- package/dist/gateway/sessions.js +3 -3
- package/dist/org/projects.js +3 -3
- package/dist/sync-sleep.js +46 -0
- package/dist/tools/task.js +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to `@nanhara/hara`.
|
|
|
5
5
|
> Versioning (pre-1.0, SemVer-style): the **minor** (middle) number bumps for a **new feature**; the
|
|
6
6
|
> **patch** (last) number bumps for **optimizations/fixes of existing features**.
|
|
7
7
|
|
|
8
|
+
## 0.122.3 — 2026-07-14 — standalone runtime recovery
|
|
9
|
+
|
|
10
|
+
- **Bun-compiled standalone binaries no longer assume `SharedArrayBuffer` exists at module startup.**
|
|
11
|
+
The short synchronous waits used by cross-process stores now prefer `Bun.sleepSync`, lazily use
|
|
12
|
+
`Atomics.wait` only on Node when its primitives exist, and retain a bounded last-resort delay. This
|
|
13
|
+
fixes the macOS arm64 `ReferenceError` that could make the 0.122.2 standalone binary exit before
|
|
14
|
+
even printing `--version`; npm/Node and GHCR users were unaffected. CI now compiles and executes a
|
|
15
|
+
native standalone binary on Linux and macOS, and the release job executes its native Linux asset
|
|
16
|
+
before publishing, so a merely successful cross-compile is no longer accepted as runtime proof.
|
|
17
|
+
|
|
8
18
|
## 0.122.2 — 2026-07-14 — reproducible container release
|
|
9
19
|
|
|
10
20
|
- **The Docker build now includes the guarded runtime entry before normalizing package modes.** This fixes the
|
|
@@ -23,9 +23,9 @@ import { getValidQwenAuth } from "../providers/qwen-oauth.js";
|
|
|
23
23
|
import { resolvePlatform } from "../providers/registry.js";
|
|
24
24
|
import { validateAgainstSchema } from "../agent/structured.js";
|
|
25
25
|
import { terminateSubprocessTree } from "../security/subprocess-env.js";
|
|
26
|
+
import { sleepSync } from "../sync-sleep.js";
|
|
26
27
|
const FILE = () => join(homedir(), ".hara", "flows-pending.json");
|
|
27
28
|
const LOCK = () => FILE() + ".lock";
|
|
28
|
-
const sleepCell = new Int32Array(new SharedArrayBuffer(4));
|
|
29
29
|
const STORE_LOCK_ATTEMPTS = 500;
|
|
30
30
|
const STORE_LOCK_WAIT_MS = 10;
|
|
31
31
|
const PENDING_STATUSES = new Set(["pending", "executing", "done", "rejected", "failed", "expired"]);
|
|
@@ -151,7 +151,7 @@ function withStoreLock(fn) {
|
|
|
151
151
|
continue;
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
-
|
|
154
|
+
sleepSync(STORE_LOCK_WAIT_MS);
|
|
155
155
|
continue;
|
|
156
156
|
}
|
|
157
157
|
const candidate = { pid: process.pid, token: randomUUID() };
|
|
@@ -190,7 +190,7 @@ function withStoreLock(fn) {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
sleepSync(STORE_LOCK_WAIT_MS);
|
|
194
194
|
}
|
|
195
195
|
if (!claim)
|
|
196
196
|
throw new Error("flows pending store is busy");
|
package/dist/gateway/sessions.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
8
8
|
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
+
import { sleepSync } from "../sync-sleep.js";
|
|
11
12
|
/** Idle window before a chat auto-rotates to a FRESH session (hours). A WeChat/Feishu chat is one
|
|
12
13
|
* endless surface — without this, days-old context piles onto every new ask and the agent answers
|
|
13
14
|
* from stale state (the "gateway answers with old context" report). Default 8h: overnight gaps start
|
|
@@ -23,7 +24,6 @@ const dir = () => join(haraDir(), "gateway");
|
|
|
23
24
|
const file = () => join(dir(), "chats.json");
|
|
24
25
|
const lockFile = () => `${file()}.lock`;
|
|
25
26
|
const reclaimFile = () => `${lockFile()}.reclaim`;
|
|
26
|
-
const waitCell = new Int32Array(new SharedArrayBuffer(4));
|
|
27
27
|
const LOCK_WAIT_MS = 5_000;
|
|
28
28
|
function errorCode(error) {
|
|
29
29
|
return typeof error === "object" && error !== null && "code" in error
|
|
@@ -114,7 +114,7 @@ function acquireLock() {
|
|
|
114
114
|
}
|
|
115
115
|
if (Date.now() >= deadline)
|
|
116
116
|
throw new Error(`Timed out waiting for gateway session store lock: ${lockFile()}`);
|
|
117
|
-
|
|
117
|
+
sleepSync(10);
|
|
118
118
|
continue;
|
|
119
119
|
}
|
|
120
120
|
try {
|
|
@@ -157,7 +157,7 @@ function acquireLock() {
|
|
|
157
157
|
if (Date.now() >= deadline) {
|
|
158
158
|
throw new Error(`Timed out waiting for gateway session store lock: ${lockFile()}`);
|
|
159
159
|
}
|
|
160
|
-
|
|
160
|
+
sleepSync(10);
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
}
|
package/dist/org/projects.js
CHANGED
|
@@ -5,8 +5,8 @@ import { isAbsolute, join, resolve } from "node:path";
|
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
import { loadRoles, loadGlobalRoles } from "./roles.js";
|
|
8
|
+
import { sleepSync } from "../sync-sleep.js";
|
|
8
9
|
const PROJECT_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/;
|
|
9
|
-
const sleepCell = new Int32Array(new SharedArrayBuffer(4));
|
|
10
10
|
const LOCK_ATTEMPTS = 500;
|
|
11
11
|
const LOCK_WAIT_MS = 10;
|
|
12
12
|
function haraDir() {
|
|
@@ -101,7 +101,7 @@ function withProjectsLock(fn) {
|
|
|
101
101
|
continue;
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
sleepSync(LOCK_WAIT_MS);
|
|
105
105
|
continue;
|
|
106
106
|
}
|
|
107
107
|
const candidate = { pid: process.pid, token: randomUUID() };
|
|
@@ -132,7 +132,7 @@ function withProjectsLock(fn) {
|
|
|
132
132
|
rmSync(reclaim, { force: true });
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
|
|
135
|
+
sleepSync(LOCK_WAIT_MS);
|
|
136
136
|
}
|
|
137
137
|
if (!claim)
|
|
138
138
|
throw new Error("projects registry is busy; retry the operation");
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Cross-runtime synchronous short wait for the tiny retry delays used by file locks.
|
|
2
|
+
//
|
|
3
|
+
// Bun standalone binaries do not guarantee that SharedArrayBuffer exists, so never construct one at
|
|
4
|
+
// module evaluation time. Prefer Bun's native primitive, use Atomics only in Node, and retain a bounded
|
|
5
|
+
// last resort so a missing/disabled runtime primitive cannot make lock acquisition crash or spin forever.
|
|
6
|
+
const MAX_SHORT_WAIT_MS = 100;
|
|
7
|
+
const MAX_FALLBACK_SPINS = 5_000_000;
|
|
8
|
+
let nodeWaitCell;
|
|
9
|
+
function boundedDelay(milliseconds) {
|
|
10
|
+
if (!Number.isFinite(milliseconds) || milliseconds <= 0)
|
|
11
|
+
return 0;
|
|
12
|
+
return Math.min(MAX_SHORT_WAIT_MS, Math.ceil(milliseconds));
|
|
13
|
+
}
|
|
14
|
+
/** Block for a small, bounded retry delay without assuming SharedArrayBuffer is available. */
|
|
15
|
+
export function sleepSync(milliseconds) {
|
|
16
|
+
const delay = boundedDelay(milliseconds);
|
|
17
|
+
if (delay === 0)
|
|
18
|
+
return;
|
|
19
|
+
const runtime = globalThis;
|
|
20
|
+
if (typeof runtime.Bun?.sleepSync === "function") {
|
|
21
|
+
try {
|
|
22
|
+
runtime.Bun.sleepSync(delay);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// A partially implemented Bun runtime still gets the bounded fallback below.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Atomics.wait is a dependable synchronous sleep on Node's main thread. Do not use this branch in
|
|
30
|
+
// Bun: a compiled Bun executable may expose part of the Node compatibility surface without SAB.
|
|
31
|
+
if (!runtime.Bun && typeof runtime.SharedArrayBuffer === "function" && typeof runtime.Atomics?.wait === "function") {
|
|
32
|
+
try {
|
|
33
|
+
nodeWaitCell ??= new Int32Array(new runtime.SharedArrayBuffer(4));
|
|
34
|
+
runtime.Atomics.wait(nodeWaitCell, 0, 0, delay);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
nodeWaitCell = undefined;
|
|
39
|
+
// Some embedders disable blocking Atomics even when the globals exist. Fall through safely.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const deadline = Date.now() + delay;
|
|
43
|
+
for (let spins = 0; spins < MAX_FALLBACK_SPINS && Date.now() < deadline; spins++) {
|
|
44
|
+
// Intentionally empty: both time and iteration ceilings bound this compatibility fallback.
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/tools/task.js
CHANGED
|
@@ -6,9 +6,9 @@ import { basename, join, resolve } from "node:path";
|
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
import { registerTool } from "./registry.js";
|
|
9
|
+
import { sleepSync } from "../sync-sleep.js";
|
|
9
10
|
const TASK_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
10
11
|
const TASK_STATUSES = new Set(["pending", "in_progress", "done"]);
|
|
11
|
-
const sleepCell = new Int32Array(new SharedArrayBuffer(4));
|
|
12
12
|
const LOCK_ATTEMPTS = 500;
|
|
13
13
|
const LOCK_WAIT_MS = 10;
|
|
14
14
|
/** Resolve aliases/symlinks before deriving the key. A missing path still gets a stable absolute key so
|
|
@@ -113,7 +113,7 @@ function withTaskLock(cwd, fn) {
|
|
|
113
113
|
continue;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
-
|
|
116
|
+
sleepSync(LOCK_WAIT_MS);
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
119
119
|
const candidate = { pid: process.pid, token: randomUUID() };
|
|
@@ -144,7 +144,7 @@ function withTaskLock(cwd, fn) {
|
|
|
144
144
|
rmSync(reclaim, { force: true });
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
sleepSync(LOCK_WAIT_MS);
|
|
148
148
|
}
|
|
149
149
|
if (!claim)
|
|
150
150
|
throw new Error("task store is busy; retry the operation");
|