@adhd/agent-core-env 0.1.2 → 0.1.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 +18 -0
- package/index.cjs +1 -1
- package/index.d.ts +6 -0
- package/index.js +92 -30
- package/open-registry-db.d.ts +6 -2
- package/open-registry-store.d.ts +15 -0
- package/package.json +2 -1
- package/sqlite-locking.d.ts +49 -0
- package/store-transaction.d.ts +36 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 0.1.3 (2026-10-06)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **agent-core-env:** adapter-backed registry open + BEGIN IMMEDIATE / busy-retry contract (ADR-0001 D4 step 1) ([6f889317](https://github.com/PseudoSky/adhd/commit/6f889317))
|
|
6
|
+
- **vite-plugins:** absorb perf/test-resolve-fix — test-time @adhd/* source resolution ([b2022986](https://github.com/PseudoSky/adhd/commit/b2022986))
|
|
7
|
+
|
|
8
|
+
### 🩹 Fixes
|
|
9
|
+
|
|
10
|
+
- **agent-core-env,agent-mcp:** BEGIN IMMEDIATE + bounded BUSY retry on AgentStore read-modify-write ([d9fddae5](https://github.com/PseudoSky/adhd/commit/d9fddae5))
|
|
11
|
+
- **agent-core-env,agent-mcp:** make the SQLite busy_timeout locking contract explicit ([c492feb8](https://github.com/PseudoSky/adhd/commit/c492feb8))
|
|
12
|
+
- **vite:** restore import.meta.url in CJS output under vite 8 ([9690844d](https://github.com/PseudoSky/adhd/commit/9690844d))
|
|
13
|
+
- **nx:** finish the ESLint v9 flat-config migration and unblock the gate ([4ccdb53e](https://github.com/PseudoSky/adhd/commit/4ccdb53e))
|
|
14
|
+
|
|
15
|
+
### ❤️ Thank You
|
|
16
|
+
|
|
17
|
+
- pseudosky
|
|
18
|
+
|
|
1
19
|
## 0.1.1 (2026-09-24)
|
|
2
20
|
|
|
3
21
|
|
package/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=require("@adhd/environment"),a=require("node:fs"),i=require("node:path"),c=require("@adhd/sox-store-adapter"),b=require("better-sqlite3"),h=require("drizzle-orm/better-sqlite3"),f="agent-registry",y={namespaces:["production"],dirs:{data:{kind:"data",scope:"global"}},files:{registry:{in:"data",name:"registry.db"}},config:{}};function u(t={}){if(t.registryDbPath)return t.registryDbPath;const e=process.env.ADHD_AGENT_REGISTRY_DB_PATH;if(e)return e;const r=process.env.REGISTRY_DATABASE_PATH;if(r)return r;const n=process.env.DATABASE_PATH;return n||new R.Environment("agent-registry",y,t.scope?{scope:t.scope}:{}).files.registry}const m=5,T=20,E=500,A=5e3;function S(t){return c.isBusyError(t)||c.isConcurrentConflict(t)}function p(t){return new Promise(e=>setTimeout(e,Math.max(0,Math.round(t))))}async function l(t,e,r={}){const n=Math.max(1,Math.trunc(r.maxAttempts??m)),s=Math.max(0,r.baseDelayMs??T),D=Math.max(s,r.maxDelayMs??E);for(let o=0;;o++)try{return await t.transaction(e,{mode:"immediate"})}catch(_){if(!S(_)||o>=n-1)throw _;const d=Math.min(D,s*2**o);await p(d*(.5+Math.random()*.5))}}async function U(t={}){const e=i.resolve(u({registryDbPath:t.registryDbPath})),r=i.dirname(e);a.existsSync(r)||a.mkdirSync(r,{recursive:!0});const n=Math.max(0,Math.trunc(t.busyTimeoutMs??A)),s=await c.createTursoAdapter({dbPath:e,concurrencyMode:"multiprocess-wal"});return typeof s.init=="function"&&await s.init(),await s.pragmaSet("busy_timeout",n),s}const M=5e3;function g(t,e={}){const r=Math.max(0,Math.trunc(e.busyTimeoutMs??M));t.pragma("journal_mode = WAL"),t.pragma("foreign_keys = ON"),t.pragma(`busy_timeout = ${r}`)}function Y(t={}){const e=i.resolve(u({registryDbPath:t.registryDbPath})),r=i.dirname(e);a.existsSync(r)||a.mkdirSync(r,{recursive:!0});const n=new b(e);g(n,{busyTimeoutMs:t.busyTimeoutMs});const s=h.drizzle(n);return{sqlite:n,db:s}}exports.AGENT_REGISTRY_PROJECT_ID=f;exports.DEFAULT_BUSY_MAX_ATTEMPTS=m;exports.DEFAULT_BUSY_RETRY_BASE_DELAY_MS=T;exports.DEFAULT_BUSY_RETRY_MAX_DELAY_MS=E;exports.DEFAULT_BUSY_TIMEOUT_MS=M;exports.DEFAULT_REGISTRY_BUSY_TIMEOUT_MS=A;exports.agentRegistryEnvironmentSpec=y;exports.applyLockingPragmas=g;exports.isBusyContention=S;exports.openRegistryDb=Y;exports.openRegistryStore=U;exports.resolveRegistryDbPath=u;exports.withImmediateRetry=l;
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export { resolveRegistryDbPath } from './resolve-registry-db-path.js';
|
|
2
2
|
export type { ResolveRegistryDbPathOpts } from './resolve-registry-db-path.js';
|
|
3
|
+
export { openRegistryStore } from './open-registry-store.js';
|
|
4
|
+
export type { OpenRegistryStoreOpts } from './open-registry-store.js';
|
|
5
|
+
export { withImmediateRetry, isBusyContention, DEFAULT_BUSY_MAX_ATTEMPTS, DEFAULT_BUSY_RETRY_BASE_DELAY_MS, DEFAULT_BUSY_RETRY_MAX_DELAY_MS, DEFAULT_REGISTRY_BUSY_TIMEOUT_MS, } from './store-transaction.js';
|
|
6
|
+
export type { BusyRetryConfig } from './store-transaction.js';
|
|
3
7
|
export { openRegistryDb } from './open-registry-db.js';
|
|
4
8
|
export type { OpenRegistryDbOpts, RegistryDbHandle } from './open-registry-db.js';
|
|
9
|
+
export { applyLockingPragmas, DEFAULT_BUSY_TIMEOUT_MS, } from './sqlite-locking.js';
|
|
10
|
+
export type { ISqliteConn, LockingPragmaOpts } from './sqlite-locking.js';
|
|
5
11
|
export { agentRegistryEnvironmentSpec, AGENT_REGISTRY_PROJECT_ID } from './spec.js';
|
|
6
12
|
export type { AgentRegistryEnvConfig } from './spec.js';
|
package/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Environment as
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
1
|
+
import { Environment as T } from "@adhd/environment";
|
|
2
|
+
import s from "node:fs";
|
|
3
|
+
import o from "node:path";
|
|
4
|
+
import { isBusyError as _, isConcurrentConflict as f, createTursoAdapter as A } from "@adhd/sox-store-adapter";
|
|
5
|
+
import M from "better-sqlite3";
|
|
6
|
+
import { drizzle as d } from "drizzle-orm/better-sqlite3";
|
|
7
|
+
const B = "agent-registry", g = {
|
|
7
8
|
namespaces: ["production"],
|
|
8
9
|
dirs: {
|
|
9
10
|
data: { kind: "data", scope: "global" }
|
|
@@ -13,33 +14,94 @@ const b = "agent-registry", m = {
|
|
|
13
14
|
},
|
|
14
15
|
config: {}
|
|
15
16
|
};
|
|
16
|
-
function
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
return
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
return
|
|
25
|
-
const
|
|
26
|
-
return
|
|
17
|
+
function m(t = {}) {
|
|
18
|
+
if (t.registryDbPath)
|
|
19
|
+
return t.registryDbPath;
|
|
20
|
+
const e = process.env.ADHD_AGENT_REGISTRY_DB_PATH;
|
|
21
|
+
if (e)
|
|
22
|
+
return e;
|
|
23
|
+
const r = process.env.REGISTRY_DATABASE_PATH;
|
|
24
|
+
if (r)
|
|
25
|
+
return r;
|
|
26
|
+
const n = process.env.DATABASE_PATH;
|
|
27
|
+
return n || new T(
|
|
27
28
|
"agent-registry",
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
g,
|
|
30
|
+
t.scope ? { scope: t.scope } : {}
|
|
30
31
|
).files.registry;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
const h = 5, D = 20, p = 500, E = 5e3;
|
|
34
|
+
function b(t) {
|
|
35
|
+
return _(t) || f(t);
|
|
36
|
+
}
|
|
37
|
+
function S(t) {
|
|
38
|
+
return new Promise(
|
|
39
|
+
(e) => setTimeout(e, Math.max(0, Math.round(t)))
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
async function L(t, e, r = {}) {
|
|
43
|
+
const n = Math.max(
|
|
44
|
+
1,
|
|
45
|
+
Math.trunc(r.maxAttempts ?? h)
|
|
46
|
+
), a = Math.max(
|
|
47
|
+
0,
|
|
48
|
+
r.baseDelayMs ?? D
|
|
49
|
+
), u = Math.max(
|
|
50
|
+
a,
|
|
51
|
+
r.maxDelayMs ?? p
|
|
52
|
+
);
|
|
53
|
+
for (let i = 0; ; i++)
|
|
54
|
+
try {
|
|
55
|
+
return await t.transaction(e, { mode: "immediate" });
|
|
56
|
+
} catch (c) {
|
|
57
|
+
if (!b(c) || i >= n - 1)
|
|
58
|
+
throw c;
|
|
59
|
+
const y = Math.min(u, a * 2 ** i);
|
|
60
|
+
await S(y * (0.5 + Math.random() * 0.5));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function I(t = {}) {
|
|
64
|
+
const e = o.resolve(
|
|
65
|
+
m({ registryDbPath: t.registryDbPath })
|
|
66
|
+
), r = o.dirname(e);
|
|
67
|
+
s.existsSync(r) || s.mkdirSync(r, { recursive: !0 });
|
|
68
|
+
const n = Math.max(
|
|
69
|
+
0,
|
|
70
|
+
Math.trunc(t.busyTimeoutMs ?? E)
|
|
71
|
+
), a = await A({
|
|
72
|
+
dbPath: e,
|
|
73
|
+
concurrencyMode: "multiprocess-wal"
|
|
74
|
+
});
|
|
75
|
+
return typeof a.init == "function" && await a.init(), await a.pragmaSet("busy_timeout", n), a;
|
|
76
|
+
}
|
|
77
|
+
const l = 5e3;
|
|
78
|
+
function R(t, e = {}) {
|
|
79
|
+
const r = Math.max(
|
|
80
|
+
0,
|
|
81
|
+
Math.trunc(e.busyTimeoutMs ?? l)
|
|
82
|
+
);
|
|
83
|
+
t.pragma("journal_mode = WAL"), t.pragma("foreign_keys = ON"), t.pragma(`busy_timeout = ${r}`);
|
|
84
|
+
}
|
|
85
|
+
function G(t = {}) {
|
|
86
|
+
const e = o.resolve(m({ registryDbPath: t.registryDbPath })), r = o.dirname(e);
|
|
87
|
+
s.existsSync(r) || s.mkdirSync(r, { recursive: !0 });
|
|
88
|
+
const n = new M(e);
|
|
89
|
+
R(n, { busyTimeoutMs: t.busyTimeoutMs });
|
|
90
|
+
const a = d(n);
|
|
91
|
+
return { sqlite: n, db: a };
|
|
39
92
|
}
|
|
40
93
|
export {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
94
|
+
B as AGENT_REGISTRY_PROJECT_ID,
|
|
95
|
+
h as DEFAULT_BUSY_MAX_ATTEMPTS,
|
|
96
|
+
D as DEFAULT_BUSY_RETRY_BASE_DELAY_MS,
|
|
97
|
+
p as DEFAULT_BUSY_RETRY_MAX_DELAY_MS,
|
|
98
|
+
l as DEFAULT_BUSY_TIMEOUT_MS,
|
|
99
|
+
E as DEFAULT_REGISTRY_BUSY_TIMEOUT_MS,
|
|
100
|
+
g as agentRegistryEnvironmentSpec,
|
|
101
|
+
R as applyLockingPragmas,
|
|
102
|
+
b as isBusyContention,
|
|
103
|
+
G as openRegistryDb,
|
|
104
|
+
I as openRegistryStore,
|
|
105
|
+
m as resolveRegistryDbPath,
|
|
106
|
+
L as withImmediateRetry
|
|
45
107
|
};
|
package/open-registry-db.d.ts
CHANGED
|
@@ -6,10 +6,14 @@ export interface OpenRegistryDbOpts {
|
|
|
6
6
|
* absent, the full precedence chain (function arg / env vars / canonical
|
|
7
7
|
* default) resolves the path. */
|
|
8
8
|
registryDbPath?: string;
|
|
9
|
+
/** `busy_timeout` budget (ms) applied at connect time. Typed config, never
|
|
10
|
+
* an env-var toggle (ADR-0013). Defaults to 5000ms. */
|
|
11
|
+
busyTimeoutMs?: number;
|
|
9
12
|
}
|
|
10
13
|
export interface RegistryDbHandle {
|
|
11
|
-
/** The raw `better-sqlite3` connection — WAL mode + `foreign_keys = ON`
|
|
12
|
-
* already set, matching every
|
|
14
|
+
/** The raw `better-sqlite3` connection — WAL mode + `foreign_keys = ON` +
|
|
15
|
+
* `busy_timeout` already set (see `sqlite-locking.ts`), matching every
|
|
16
|
+
* existing family client's pragmas. */
|
|
13
17
|
sqlite: Database.Database;
|
|
14
18
|
/** A schema-less Drizzle instance over `sqlite`. Each family package
|
|
15
19
|
* binds its OWN schema-typed Drizzle instance against this same
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StoreAdapter } from '@adhd/sox-store-adapter';
|
|
2
|
+
|
|
3
|
+
export interface OpenRegistryStoreOpts {
|
|
4
|
+
/** Explicit path override — forwarded to `resolveRegistryDbPath()`. */
|
|
5
|
+
registryDbPath?: string;
|
|
6
|
+
/** `busy_timeout` budget (ms) applied at connect time. Typed config, never
|
|
7
|
+
* an env-var toggle (sox ADR-0013). Defaults to 5000 ms. */
|
|
8
|
+
busyTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Opens (and, if needed, creates) the shared registry store through
|
|
12
|
+
* `@adhd/sox-store-adapter` (Turso, multiprocess-enabled), applying the typed
|
|
13
|
+
* `busy_timeout`. The caller owns the returned adapter and must `close()` it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function openRegistryStore(opts?: OpenRegistryStoreOpts): Promise<StoreAdapter>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/agent-core-env",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Shared @adhd/environment-backed resolver for the agent-registry family's one shared SQLite file (agent-store-prompts/-tools, agent-core-policy/-provider, agent-engine-compiler) — resolveRegistryDbPath() + lazy openRegistryDb(), no import-time DB open.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@adhd/environment": "^0.1.8",
|
|
17
|
+
"@adhd/sox-store-adapter": "^0.13.0",
|
|
17
18
|
"better-sqlite3": "12.10.0",
|
|
18
19
|
"drizzle-orm": "0.45.2"
|
|
19
20
|
},
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sqlite-locking.ts` — the explicit parallel-process locking contract for
|
|
3
|
+
* the agent-* SQLite clients.
|
|
4
|
+
*
|
|
5
|
+
* The repo invariant (AGENTS.md hard rule; ADR-0012, superseding ADR-0007) is
|
|
6
|
+
* that every store is parallel-process enabled: multiple processes may hold
|
|
7
|
+
* concurrent write connections to the same SQLite file. This module makes the
|
|
8
|
+
* connect-time half of that contract EXPLICIT, CONFIGURABLE and TYPED (never
|
|
9
|
+
* an env-var toggle — ADR-0013):
|
|
10
|
+
*
|
|
11
|
+
* 1. `journal_mode = WAL` — readers do not block the writer.
|
|
12
|
+
* 2. `foreign_keys = ON`.
|
|
13
|
+
* 3. `busy_timeout = N` (ms) — a writer that loses the lock race waits a
|
|
14
|
+
* bounded time for the holder before surfacing an error.
|
|
15
|
+
*
|
|
16
|
+
* SCOPE NOTE (ADR-0001, backlog 331508ac): `better-sqlite3`-as-the-store is
|
|
17
|
+
* RETIRED. The sanctioned substrate is the sox store adapter (Turso) — see
|
|
18
|
+
* `open-registry-store.ts` (the adapter-backed open, applying the same
|
|
19
|
+
* `busy_timeout` via `adapter.pragmaSet`) and `store-transaction.ts` (the
|
|
20
|
+
* `BEGIN IMMEDIATE` + bounded busy-only retry contract, on top of the adapter).
|
|
21
|
+
*
|
|
22
|
+
* This module remains ONLY for the `better-sqlite3` consumers the ADR's D4
|
|
23
|
+
* sequence has not yet migrated (Drizzle-based stores still receive a
|
|
24
|
+
* better-sqlite3 handle). It is not a second locking implementation: it is the
|
|
25
|
+
* connect-time pragma trio applied to the not-yet-migrated substrate. New
|
|
26
|
+
* stores MUST start on the adapter (ADR-0001 D1).
|
|
27
|
+
*/
|
|
28
|
+
/** Default `busy_timeout` budget in milliseconds. Long enough to absorb a
|
|
29
|
+
* sibling process's short write transaction, short enough that a genuinely
|
|
30
|
+
* wedged holder still surfaces as an error rather than an infinite hang. */
|
|
31
|
+
export declare const DEFAULT_BUSY_TIMEOUT_MS = 5000;
|
|
32
|
+
/** Minimal structural type for a better-sqlite3 connection — avoids importing
|
|
33
|
+
* the runtime class merely to type the pragma call. */
|
|
34
|
+
export interface ISqliteConn {
|
|
35
|
+
pragma(source: string, options?: {
|
|
36
|
+
simple: boolean;
|
|
37
|
+
}): unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface LockingPragmaOpts {
|
|
40
|
+
/** `busy_timeout` budget in ms. Defaults to {@link DEFAULT_BUSY_TIMEOUT_MS}. */
|
|
41
|
+
busyTimeoutMs?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Applies the connect-time locking pragmas to a raw SQLite connection:
|
|
45
|
+
* `journal_mode = WAL`, `foreign_keys = ON`, `busy_timeout = N`. Safe to call
|
|
46
|
+
* more than once. This is the ONE place the pragma trio is defined so every
|
|
47
|
+
* agent-* client stays in sync.
|
|
48
|
+
*/
|
|
49
|
+
export declare function applyLockingPragmas(sqlite: ISqliteConn, opts?: LockingPragmaOpts): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AdapterTransaction, StoreAdapter } from '@adhd/sox-store-adapter';
|
|
2
|
+
|
|
3
|
+
/** Total attempts (first try + retries). Default 5 — the backlog precedent. */
|
|
4
|
+
export declare const DEFAULT_BUSY_MAX_ATTEMPTS = 5;
|
|
5
|
+
/** Base backoff in ms (doubles per attempt, then jittered). Default 20. */
|
|
6
|
+
export declare const DEFAULT_BUSY_RETRY_BASE_DELAY_MS = 20;
|
|
7
|
+
/** Per-sleep cap in ms. Default 500. */
|
|
8
|
+
export declare const DEFAULT_BUSY_RETRY_MAX_DELAY_MS = 500;
|
|
9
|
+
/** Default `busy_timeout` (ms) applied to every migrated registry connection. */
|
|
10
|
+
export declare const DEFAULT_REGISTRY_BUSY_TIMEOUT_MS = 5000;
|
|
11
|
+
export interface BusyRetryConfig {
|
|
12
|
+
/** Total attempts. Default {@link DEFAULT_BUSY_MAX_ATTEMPTS}; clamped to ≥1. */
|
|
13
|
+
maxAttempts?: number;
|
|
14
|
+
/** Base backoff in ms. Default {@link DEFAULT_BUSY_RETRY_BASE_DELAY_MS}. */
|
|
15
|
+
baseDelayMs?: number;
|
|
16
|
+
/** Per-sleep cap in ms. Default {@link DEFAULT_BUSY_RETRY_MAX_DELAY_MS}. */
|
|
17
|
+
maxDelayMs?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* True iff `err` is a busy/locked contention shape worth retrying — the
|
|
21
|
+
* adapter's portable classification (`isBusyError` / `isConcurrentConflict`),
|
|
22
|
+
* so the taxonomy stays driver-agnostic and single-sourced.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isBusyContention(err: unknown): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Run `fn` inside `adapter.transaction(fn, { mode: 'immediate' })`, retrying the
|
|
27
|
+
* WHOLE transaction a bounded number of times ONLY when it throws a busy-shaped
|
|
28
|
+
* error (see {@link isBusyContention}). Any other error propagates immediately,
|
|
29
|
+
* unretried; after the final attempt still fails busy, the last busy error is
|
|
30
|
+
* re-thrown (a sustained pileup is a real failure — this bounds the wait, it
|
|
31
|
+
* does not hide contention forever).
|
|
32
|
+
*
|
|
33
|
+
* `adapter` is narrowed to the one method this needs, so a test double can
|
|
34
|
+
* supply a minimal `{ transaction }` shape.
|
|
35
|
+
*/
|
|
36
|
+
export declare function withImmediateRetry<T>(adapter: Pick<StoreAdapter, 'transaction'>, fn: (tx: AdapterTransaction) => T | Promise<T>, opts?: BusyRetryConfig): Promise<T>;
|