wicked-brain 0.8.0 → 0.8.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-brain",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "Digital brain as skills for AI coding CLIs — no vector DB, no embeddings, no infrastructure",
6
6
  "keywords": [
@@ -27,26 +27,41 @@ if (args.includes("--version") || args.includes("-v")) {
27
27
 
28
28
  const brainPath = resolve(getArg("brain") || ".");
29
29
  const preferredPort = parseInt(getArg("port") || "4242", 10);
30
+ // Explicit --port means "bind this exact port or fail" — no probe.
31
+ const portExplicit = args.includes("--port");
30
32
  const configPath = join(brainPath, "brain.json");
31
33
  // Source path for LSP workspace root — prefer --source flag, fall back to config, then brainPath
32
34
  const sourceArgRaw = getArg("source");
33
35
  const sourceArg = sourceArgRaw ? resolve(sourceArgRaw) : null;
34
36
 
35
- /** Find a free TCP port starting from `start`. */
36
- function findFreePort(start) {
37
+ /**
38
+ * Listen on `startPort`, probing upward on EADDRINUSE. Probes using the real
39
+ * server instance so the bind semantics (dual-stack IPv4+IPv6) match the
40
+ * eventual listener — a separate 127.0.0.1 probe would miss an IPv6-only
41
+ * conflict and produce a false "free" result.
42
+ */
43
+ function listenWithProbe(server, startPort, maxProbe) {
37
44
  return new Promise((resolve, reject) => {
38
- const tryPort = (p) => {
39
- const probe = createServer();
40
- probe.once("error", (err) => {
41
- if (err.code === "EADDRINUSE") tryPort(p + 1);
42
- else reject(err);
43
- });
44
- probe.once("listening", () => {
45
- probe.close(() => resolve(p));
46
- });
47
- probe.listen(p, "127.0.0.1");
45
+ let p = startPort;
46
+ const attempt = () => {
47
+ const onError = (err) => {
48
+ server.off("listening", onListen);
49
+ if (err.code === "EADDRINUSE" && p < startPort + maxProbe - 1) {
50
+ p += 1;
51
+ attempt();
52
+ return;
53
+ }
54
+ reject(err);
55
+ };
56
+ const onListen = () => {
57
+ server.off("error", onError);
58
+ resolve(p);
59
+ };
60
+ server.once("error", onError);
61
+ server.once("listening", onListen);
62
+ server.listen(p);
48
63
  };
49
- tryPort(start);
64
+ attempt();
50
65
  });
51
66
  }
52
67
 
@@ -239,9 +254,13 @@ watcher.onFileChange((relPath, absPath, content, eventType) => {
239
254
  lsp.handleFileChange(relPath, absPath, content, eventType);
240
255
  });
241
256
 
242
- const port = await findFreePort(preferredPort);
257
+ // Bind the real server. Probe upward on EADDRINUSE unless the user passed
258
+ // --port explicitly (in which case bind that port or fail loudly).
259
+ const port = await listenWithProbe(server, preferredPort, portExplicit ? 1 : 20);
243
260
 
244
- // Write actual port back to config so skills can always find the server
261
+ // Write actual bound port back to config so skills can always find the server.
262
+ // Must happen AFTER listen succeeds — a pre-listen write would leave a stale
263
+ // value if the bind failed.
245
264
  try {
246
265
  let metaConfig = {};
247
266
  try { metaConfig = JSON.parse(readFileSync(metaConfigPath, "utf-8")); } catch {}
@@ -251,18 +270,16 @@ try {
251
270
  console.error(`Warning: could not write port to config: ${err.message}`);
252
271
  }
253
272
 
254
- server.listen(port, async () => {
255
- console.log(`wicked-brain-server running on port ${port} (brain: ${brainId}, pid: ${pid})`);
256
- watcher.start();
257
- const busReady = await waitForBus();
258
- emitEvent("wicked.server.started", "brain.system", {
259
- brain_id: brainId, port, pid,
260
- });
261
- if (busReady) {
262
- try {
263
- memorySubscriber = await startMemorySubscriber({ brainPath, brainId, db });
264
- } catch (err) {
265
- console.error(`[memory-subscriber] failed to start: ${err.message}`);
266
- }
267
- }
273
+ console.log(`wicked-brain-server running on port ${port} (brain: ${brainId}, pid: ${pid})`);
274
+ watcher.start();
275
+ const busReady = await waitForBus();
276
+ emitEvent("wicked.server.started", "brain.system", {
277
+ brain_id: brainId, port, pid,
268
278
  });
279
+ if (busReady) {
280
+ try {
281
+ memorySubscriber = await startMemorySubscriber({ brainPath, brainId, db });
282
+ } catch (err) {
283
+ console.error(`[memory-subscriber] failed to start: ${err.message}`);
284
+ }
285
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wicked-brain-server",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "SQLite FTS5 search server for wicked-brain digital knowledge bases",
6
6
  "keywords": [