negotium 0.2.19 → 0.2.21

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.
Files changed (56) hide show
  1. package/dist/agent-helpers.js +573 -593
  2. package/dist/agent-helpers.js.map +17 -17
  3. package/dist/background-bash.js +3 -2
  4. package/dist/background-bash.js.map +3 -3
  5. package/dist/browser-runtime.js +76 -36
  6. package/dist/browser-runtime.js.map +6 -6
  7. package/dist/canonical-mcp-bridge.js +1 -1
  8. package/dist/{chunk-fc58cqry.js → chunk-rhp26p2c.js} +171 -26
  9. package/dist/{chunk-fc58cqry.js.map → chunk-rhp26p2c.js.map} +6 -5
  10. package/dist/{chunk-r0xs3t81.js → chunk-s2gez3wg.js} +18 -3
  11. package/dist/{chunk-r0xs3t81.js.map → chunk-s2gez3wg.js.map} +2 -2
  12. package/dist/hosted-agent.js +185 -54
  13. package/dist/hosted-agent.js.map +7 -6
  14. package/dist/main.js +1209 -1114
  15. package/dist/main.js.map +25 -25
  16. package/dist/mcp-catalog.js.map +1 -1
  17. package/dist/mcp-factories.js +526 -540
  18. package/dist/mcp-factories.js.map +17 -17
  19. package/dist/outbox.js.map +1 -1
  20. package/dist/platform-runtime.js.map +1 -1
  21. package/dist/prompts.js +3 -2
  22. package/dist/prompts.js.map +3 -3
  23. package/dist/query-runtime.js +63 -26
  24. package/dist/query-runtime.js.map +4 -4
  25. package/dist/registry.js +4 -3
  26. package/dist/registry.js.map +3 -3
  27. package/dist/rollout.js +1 -1
  28. package/dist/runtime/src/agents/archiver.ts +3 -2
  29. package/dist/runtime/src/application/switch-topic-access-mode.ts +81 -8
  30. package/dist/runtime/src/bus.ts +19 -2
  31. package/dist/runtime/src/mcp/session-comm/runtime.ts +16 -0
  32. package/dist/runtime/src/platform/config.ts +13 -1
  33. package/dist/runtime/src/runtime/ask-callbacks.ts +19 -11
  34. package/dist/runtime/src/runtime/background-sessions.ts +9 -7
  35. package/dist/runtime/src/runtime/visual-store.ts +31 -23
  36. package/dist/runtime/src/storage/api-topics.ts +26 -0
  37. package/dist/runtime/src/storage/browser-profiles.ts +16 -8
  38. package/dist/runtime/src/storage/runtime-events.ts +30 -20
  39. package/dist/runtime/src/storage/runtime-gateway-submissions.ts +24 -16
  40. package/dist/runtime/src/storage/runtime-turn-requests.ts +9 -1
  41. package/dist/runtime/src/storage/self-schedules.ts +32 -24
  42. package/dist/runtime/src/storage/storage-host.ts +105 -43
  43. package/dist/runtime/src/storage/vault.ts +12 -3
  44. package/dist/runtime/src/version.ts +1 -1
  45. package/dist/runtime-helpers.js +63 -26
  46. package/dist/runtime-helpers.js.map +4 -4
  47. package/dist/sqlite.js +17 -1
  48. package/dist/sqlite.js.map +2 -2
  49. package/dist/storage.js +92 -46
  50. package/dist/storage.js.map +4 -4
  51. package/dist/types/packages/core/src/storage/api-topics.d.ts +15 -0
  52. package/dist/types/packages/core/src/storage/storage-host.d.ts +10 -0
  53. package/dist/types/packages/core/src/version.d.ts +1 -1
  54. package/dist/vault.js +156 -10
  55. package/dist/vault.js.map +6 -5
  56. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { db } from "#storage/forum-db";
2
+ import { registerStorageSchemaInitializer } from "#storage/storage-host";
2
3
 
3
4
  export const RUNTIME_EVENT_TYPES = [
4
5
  "message",
@@ -29,26 +30,35 @@ interface RuntimeEventRow {
29
30
  created_at: string;
30
31
  }
31
32
 
32
- db.exec(`
33
- CREATE TABLE IF NOT EXISTS runtime_events (
34
- seq INTEGER PRIMARY KEY AUTOINCREMENT,
35
- source_id TEXT NOT NULL,
36
- event_type TEXT NOT NULL CHECK (
37
- event_type IN (
38
- 'message',
39
- 'message-updated',
40
- 'ai-status',
41
- 'topic-created',
42
- 'topic-updated',
43
- 'topic-deleted'
44
- )
45
- ),
46
- topic_id TEXT NOT NULL,
47
- payload_json TEXT NOT NULL,
48
- created_at TEXT NOT NULL
49
- )
50
- `);
51
- db.exec("CREATE INDEX IF NOT EXISTS idx_runtime_events_topic_seq ON runtime_events(topic_id, seq)");
33
+ // Registered rather than executed at import time. A bare top-level `db.exec`
34
+ // resolves the storage connection the moment this module is imported, which for
35
+ // an embedding host is before `configureStorageHost()` has run — Negotium then
36
+ // opens its own database in the default state directory, caches it, and every
37
+ // later caller silently keeps using it instead of the host's.
38
+ registerStorageSchemaInitializer((database) => {
39
+ database.exec(`
40
+ CREATE TABLE IF NOT EXISTS runtime_events (
41
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
42
+ source_id TEXT NOT NULL,
43
+ event_type TEXT NOT NULL CHECK (
44
+ event_type IN (
45
+ 'message',
46
+ 'message-updated',
47
+ 'ai-status',
48
+ 'topic-created',
49
+ 'topic-updated',
50
+ 'topic-deleted'
51
+ )
52
+ ),
53
+ topic_id TEXT NOT NULL,
54
+ payload_json TEXT NOT NULL,
55
+ created_at TEXT NOT NULL
56
+ )
57
+ `);
58
+ database.exec(
59
+ "CREATE INDEX IF NOT EXISTS idx_runtime_events_topic_seq ON runtime_events(topic_id, seq)",
60
+ );
61
+ });
52
62
 
53
63
  const types = new Set<string>(RUNTIME_EVENT_TYPES);
54
64
 
@@ -1,4 +1,5 @@
1
1
  import { db } from "#storage/forum-db";
2
+ import { registerStorageSchemaInitializer } from "#storage/storage-host";
2
3
 
3
4
  export interface RuntimeGatewaySubmission {
4
5
  clientMessageId: string;
@@ -25,19 +26,29 @@ interface RuntimeGatewaySubmissionRow {
25
26
  payload_hash: string | null;
26
27
  }
27
28
 
28
- db.exec(`
29
- CREATE TABLE IF NOT EXISTS runtime_gateway_submissions (
30
- client_message_id TEXT PRIMARY KEY,
31
- request_id TEXT NOT NULL UNIQUE,
32
- topic_id TEXT NOT NULL,
33
- message_id TEXT NOT NULL,
34
- user_id TEXT NOT NULL,
35
- created_at TEXT NOT NULL,
36
- ack_cursor INTEGER NOT NULL DEFAULT 0,
37
- message_cursor INTEGER NOT NULL DEFAULT 0,
38
- payload_hash TEXT
39
- )
40
- `);
29
+ // Registered rather than executed at import time. A bare top-level `db.exec`
30
+ // resolves the storage connection the moment this module is imported, which for
31
+ // an embedding host is before `configureStorageHost()` has run — Negotium then
32
+ // opens its own database in the default state directory, caches it, and every
33
+ // later caller silently keeps using it instead of the host's.
34
+ registerStorageSchemaInitializer((database) => {
35
+ database.exec(`
36
+ CREATE TABLE IF NOT EXISTS runtime_gateway_submissions (
37
+ client_message_id TEXT PRIMARY KEY,
38
+ request_id TEXT NOT NULL UNIQUE,
39
+ topic_id TEXT NOT NULL,
40
+ message_id TEXT NOT NULL,
41
+ user_id TEXT NOT NULL,
42
+ created_at TEXT NOT NULL,
43
+ ack_cursor INTEGER NOT NULL DEFAULT 0,
44
+ message_cursor INTEGER NOT NULL DEFAULT 0,
45
+ payload_hash TEXT
46
+ )
47
+ `);
48
+ database.exec(
49
+ "CREATE INDEX IF NOT EXISTS idx_runtime_gateway_submissions_topic ON runtime_gateway_submissions(topic_id)",
50
+ );
51
+ });
41
52
  try {
42
53
  db.exec(
43
54
  "ALTER TABLE runtime_gateway_submissions ADD COLUMN ack_cursor INTEGER NOT NULL DEFAULT 0",
@@ -51,9 +62,6 @@ try {
51
62
  "ALTER TABLE runtime_gateway_submissions ADD COLUMN message_cursor INTEGER NOT NULL DEFAULT 0",
52
63
  );
53
64
  } catch {}
54
- db.exec(
55
- "CREATE INDEX IF NOT EXISTS idx_runtime_gateway_submissions_topic ON runtime_gateway_submissions(topic_id)",
56
- );
57
65
 
58
66
  function rowToSubmission(row: RuntimeGatewaySubmissionRow): RuntimeGatewaySubmission {
59
67
  return {
@@ -9,6 +9,7 @@ import { db } from "#storage/forum-db";
9
9
  import { TURN_LEASE_STALE_MS } from "#storage/runtime-leases";
10
10
  import { getRuntimeTopicEpoch, TOPIC_MAINTENANCE_STALE_MS } from "#storage/runtime-topic-state";
11
11
  import type { StorageDatabase } from "#storage/storage-contract";
12
+ import { registerStorageSchemaInitializer } from "#storage/storage-host";
12
13
  import type { AgentKind, EffortLevel, PeerRuntimeBridgeContext } from "#types";
13
14
 
14
15
  const REQUEST_CLAIM_STALE_MS = TURN_LEASE_STALE_MS;
@@ -149,7 +150,14 @@ export function ensureRuntimeUserTurnRequestsSchema(database: StorageDatabase):
149
150
  );
150
151
  }
151
152
 
152
- ensureRuntimeUserTurnRequestsSchema(db);
153
+ // Registered rather than run at import time. Calling this with `db` on module
154
+ // evaluation resolves the storage connection immediately, which for an
155
+ // embedding host happens before `configureStorageHost()` — Negotium then opens
156
+ // and caches its own database in the default state directory, and every later
157
+ // caller silently keeps using it instead of the host's.
158
+ registerStorageSchemaInitializer((database) =>
159
+ ensureRuntimeUserTurnRequestsSchema(database as unknown as StorageDatabase),
160
+ );
153
161
 
154
162
  function rowToRequest(row: RuntimeUserTurnRequestRow): RuntimeUserTurnRequest {
155
163
  let attachments: string[] | undefined;
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { db } from "#storage/forum-db";
3
3
  import { TURN_LEASE_STALE_MS } from "#storage/runtime-leases";
4
4
  import { TOPIC_MAINTENANCE_STALE_MS } from "#storage/runtime-topic-state";
5
+ import { registerStorageSchemaInitializer } from "#storage/storage-host";
5
6
 
6
7
  export type SelfScheduleStatus = "pending" | "running";
7
8
 
@@ -33,30 +34,37 @@ interface SelfScheduleRow {
33
34
  running_query_id: string | null;
34
35
  }
35
36
 
36
- db.exec(`
37
- CREATE TABLE IF NOT EXISTS runtime_self_schedules (
38
- id TEXT PRIMARY KEY,
39
- topic_id TEXT NOT NULL,
40
- user_id TEXT NOT NULL,
41
- message TEXT NOT NULL,
42
- deliver_at INTEGER NOT NULL,
43
- created_at INTEGER NOT NULL,
44
- updated_at INTEGER NOT NULL,
45
- status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running')),
46
- claimed_by TEXT,
47
- claimed_at INTEGER,
48
- running_query_id TEXT
49
- )
50
- `);
51
- db.exec(`
52
- CREATE UNIQUE INDEX IF NOT EXISTS idx_runtime_self_schedules_one_pending
53
- ON runtime_self_schedules(topic_id)
54
- WHERE status = 'pending'
55
- `);
56
- db.exec(`
57
- CREATE INDEX IF NOT EXISTS idx_runtime_self_schedules_due
58
- ON runtime_self_schedules(status, deliver_at)
59
- `);
37
+ // Registered rather than executed at import time. A bare top-level `db.exec`
38
+ // resolves the storage connection the moment this module is imported, which for
39
+ // an embedding host is before `configureStorageHost()` has run — Negotium then
40
+ // opens its own database in the default state directory, caches it, and every
41
+ // later caller silently keeps using it instead of the host's.
42
+ registerStorageSchemaInitializer((database) => {
43
+ database.exec(`
44
+ CREATE TABLE IF NOT EXISTS runtime_self_schedules (
45
+ id TEXT PRIMARY KEY,
46
+ topic_id TEXT NOT NULL,
47
+ user_id TEXT NOT NULL,
48
+ message TEXT NOT NULL,
49
+ deliver_at INTEGER NOT NULL,
50
+ created_at INTEGER NOT NULL,
51
+ updated_at INTEGER NOT NULL,
52
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running')),
53
+ claimed_by TEXT,
54
+ claimed_at INTEGER,
55
+ running_query_id TEXT
56
+ )
57
+ `);
58
+ database.exec(`
59
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_runtime_self_schedules_one_pending
60
+ ON runtime_self_schedules(topic_id)
61
+ WHERE status = 'pending'
62
+ `);
63
+ database.exec(`
64
+ CREATE INDEX IF NOT EXISTS idx_runtime_self_schedules_due
65
+ ON runtime_self_schedules(status, deliver_at)
66
+ `);
67
+ });
60
68
 
61
69
  function rowToSchedule(row: SelfScheduleRow): SelfSchedule {
62
70
  return {
@@ -14,28 +14,73 @@ export type {
14
14
  StorageTransaction,
15
15
  } from "#storage/storage-contract";
16
16
 
17
- let configuredHost: Readonly<StorageHostConfig> = {};
18
17
  type InternalStorageDatabase = InstanceType<typeof Database>;
19
18
  type OwnedStorageDatabase = InternalStorageDatabase & { close(): void };
20
- let fallbackDatabase: OwnedStorageDatabase | null = null;
21
- let fallbackDatabasePath: string | null = null;
22
19
 
23
20
  interface StorageHostFrame {
24
21
  active: boolean;
25
22
  patch: Readonly<StorageHostConfig>;
26
23
  }
27
24
 
28
- const storageHostFrames: StorageHostFrame[] = [];
29
-
30
25
  type StorageSchemaInitializer = (database: InternalStorageDatabase) => void;
31
26
  interface RegisteredSchemaInitializer {
32
27
  initialize: StorageSchemaInitializer;
33
28
  priority: number;
34
29
  }
35
30
 
36
- const schemaInitializers: RegisteredSchemaInitializer[] = [];
37
- const initializedSchemas = new WeakMap<InternalStorageDatabase, Set<StorageSchemaInitializer>>();
38
- const initializingDatabases = new WeakSet<InternalStorageDatabase>();
31
+ interface StorageHostState {
32
+ configuredHost: Readonly<StorageHostConfig>;
33
+ fallbackDatabase: OwnedStorageDatabase | null;
34
+ fallbackDatabasePath: string | null;
35
+ frames: StorageHostFrame[];
36
+ schemaInitializers: RegisteredSchemaInitializer[];
37
+ initializedSchemas: WeakMap<InternalStorageDatabase, Set<StorageSchemaInitializer>>;
38
+ initializingDatabases: WeakSet<InternalStorageDatabase>;
39
+ }
40
+
41
+ /**
42
+ * Which database an embedded Negotium uses is a property of the *process*, so
43
+ * the state deciding it is keyed off a registered symbol rather than held in
44
+ * module scope.
45
+ *
46
+ * Module scope would be equivalent only if every caller resolved to the same
47
+ * module instance, and in the published package they do not:
48
+ * `build-negotium-package.ts` emits most public entrypoints as independent
49
+ * graphs, so each gets a private copy of this file. A host that configured
50
+ * `negotium/storage` therefore left `negotium/agent-helpers` unconfigured, and
51
+ * that copy opened a second database under the default state directory — with
52
+ * no error, since both files are valid and writable.
53
+ *
54
+ * Merging those entrypoints into one graph looks tidier and does cut ~3MB from
55
+ * `dist`, but Bun 1.2.15 (the pinned version) emits a shared chunk's export
56
+ * list twice and the result is invalid ESM — "Cannot export a duplicate name".
57
+ * That is bundler output with no counterpart in this source, so there is
58
+ * nothing to de-duplicate here; it is fixed in Bun 1.3.14. Until the floor
59
+ * moves, correctness cannot depend on how the package is chunked.
60
+ */
61
+ const STORAGE_HOST_STATE = Symbol.for("negotium.storage-host.state.v1");
62
+
63
+ function storageState(): StorageHostState {
64
+ const holder = globalThis as typeof globalThis & { [STORAGE_HOST_STATE]?: StorageHostState };
65
+ const existing = holder[STORAGE_HOST_STATE];
66
+ if (existing) return existing;
67
+ const created: StorageHostState = {
68
+ configuredHost: {},
69
+ fallbackDatabase: null,
70
+ fallbackDatabasePath: null,
71
+ frames: [],
72
+ schemaInitializers: [],
73
+ initializedSchemas: new WeakMap(),
74
+ initializingDatabases: new WeakSet(),
75
+ };
76
+ Object.defineProperty(holder, STORAGE_HOST_STATE, {
77
+ value: created,
78
+ enumerable: false,
79
+ writable: false,
80
+ configurable: false,
81
+ });
82
+ return created;
83
+ }
39
84
 
40
85
  function envPath(name: string, fallback: string): string {
41
86
  const value = process.env[name]?.trim();
@@ -114,41 +159,55 @@ export function initializeDatabase(database: InternalStorageDatabase): void {
114
159
 
115
160
  function defaultDatabase(): InternalStorageDatabase {
116
161
  const path = defaultSessionsDatabasePath();
117
- if (fallbackDatabase && fallbackDatabasePath === path) return fallbackDatabase;
118
- if (fallbackDatabase) fallbackDatabase.close();
162
+ const state = storageState();
163
+ if (state.fallbackDatabase && state.fallbackDatabasePath === path) return state.fallbackDatabase;
164
+ if (state.fallbackDatabase) state.fallbackDatabase.close();
119
165
  mkdirSync(dirname(path), { recursive: true });
120
- fallbackDatabase = new Database(path, { create: true }) as unknown as OwnedStorageDatabase;
121
- fallbackDatabasePath = path;
122
- initializeDatabase(fallbackDatabase);
123
- return fallbackDatabase;
166
+ state.fallbackDatabase = new Database(path, { create: true }) as unknown as OwnedStorageDatabase;
167
+ state.fallbackDatabasePath = path;
168
+ initializeDatabase(state.fallbackDatabase);
169
+ return state.fallbackDatabase;
124
170
  }
125
171
 
126
172
  export function resolveStorageDatabase(): InternalStorageDatabase {
127
- return (configuredHost.database ?? defaultDatabase()) as InternalStorageDatabase;
173
+ return (storageState().configuredHost.database ?? defaultDatabase()) as InternalStorageDatabase;
174
+ }
175
+
176
+ /**
177
+ * The host-injected connection, or null when Negotium owns its own store.
178
+ *
179
+ * Unlike {@link resolveStorageDatabase} this never opens anything. Callers that
180
+ * manage their own short-lived connections need to know whether a host store
181
+ * exists *before* deciding to open one by path: opening it anyway is how an
182
+ * embedded Negotium ends up writing half its state into the host's database and
183
+ * the other half into `~/.negotium`, with nothing reporting an error.
184
+ */
185
+ export function configuredStorageDatabase(): InternalStorageDatabase | null {
186
+ return (storageState().configuredHost.database as InternalStorageDatabase | undefined) ?? null;
128
187
  }
129
188
 
130
189
  export function resolveStorageDataDir(): string {
131
- return configuredHost.dataDir ?? defaultDataDir();
190
+ return storageState().configuredHost.dataDir ?? defaultDataDir();
132
191
  }
133
192
 
134
193
  export function resolveStorageLogDir(): string {
135
- return configuredHost.logDir ?? defaultLogDir();
194
+ return storageState().configuredHost.logDir ?? defaultLogDir();
136
195
  }
137
196
 
138
197
  export function resolveStorageSessionAsksDir(): string {
139
- return configuredHost.sessionAsksDir ?? defaultSessionAsksDir();
198
+ return storageState().configuredHost.sessionAsksDir ?? defaultSessionAsksDir();
140
199
  }
141
200
 
142
201
  export function resolveStorageWorkspaceDir(): string {
143
- return configuredHost.workspaceDir ?? defaultWorkspaceDir();
202
+ return storageState().configuredHost.workspaceDir ?? defaultWorkspaceDir();
144
203
  }
145
204
 
146
205
  export function resolveStorageSharedWikiDir(): string {
147
- return configuredHost.sharedWikiDir ?? join(resolveStorageWorkspaceDir(), "wiki");
206
+ return storageState().configuredHost.sharedWikiDir ?? join(resolveStorageWorkspaceDir(), "wiki");
148
207
  }
149
208
 
150
209
  export function resolveStorageUsersLogDir(): string {
151
- return configuredHost.usersLogDir ?? join(resolveStorageDataDir(), "users");
210
+ return storageState().configuredHost.usersLogDir ?? join(resolveStorageDataDir(), "users");
152
211
  }
153
212
 
154
213
  const STORAGE_PATH_KEYS = [
@@ -176,11 +235,9 @@ function normalizeStorageHostPatch(options: StorageHostConfig): Readonly<Storage
176
235
  }
177
236
 
178
237
  function refreshConfiguredHost(): void {
179
- configuredHost = Object.freeze(
180
- Object.assign(
181
- {},
182
- ...storageHostFrames.filter((frame) => frame.active).map((frame) => frame.patch),
183
- ),
238
+ const state = storageState();
239
+ state.configuredHost = Object.freeze(
240
+ Object.assign({}, ...state.frames.filter((frame) => frame.active).map((frame) => frame.patch)),
184
241
  );
185
242
  }
186
243
 
@@ -193,52 +250,57 @@ function refreshConfiguredHost(): void {
193
250
  */
194
251
  export function configureStorageHost(options: StorageHostConfig): () => void {
195
252
  const frame: StorageHostFrame = { active: true, patch: normalizeStorageHostPatch(options) };
196
- storageHostFrames.push(frame);
253
+ storageState().frames.push(frame);
197
254
  refreshConfiguredHost();
198
255
  return () => {
199
256
  if (!frame.active) return;
200
257
  frame.active = false;
201
- const index = storageHostFrames.indexOf(frame);
202
- if (index >= 0) storageHostFrames.splice(index, 1);
258
+ const frames = storageState().frames;
259
+ const index = frames.indexOf(frame);
260
+ if (index >= 0) frames.splice(index, 1);
203
261
  refreshConfiguredHost();
204
262
  };
205
263
  }
206
264
 
207
265
  /** Remove every configured host layer and restore standalone fallbacks. */
208
266
  export function resetStorageHost(): void {
209
- for (const frame of storageHostFrames) frame.active = false;
210
- storageHostFrames.length = 0;
267
+ const frames = storageState().frames;
268
+ for (const frame of frames) frame.active = false;
269
+ frames.length = 0;
211
270
  refreshConfiguredHost();
212
271
  }
213
272
 
214
273
  /** Close only Negotium's fallback connection. Injected connections are borrowed. */
215
274
  export function closeStorageDatabase(): void {
216
- if (!fallbackDatabase) return;
217
- fallbackDatabase.close();
218
- fallbackDatabase = null;
219
- fallbackDatabasePath = null;
275
+ const state = storageState();
276
+ if (!state.fallbackDatabase) return;
277
+ state.fallbackDatabase.close();
278
+ state.fallbackDatabase = null;
279
+ state.fallbackDatabasePath = null;
220
280
  }
221
281
 
222
282
  export function registerStorageSchemaInitializer(
223
283
  initialize: StorageSchemaInitializer,
224
284
  priority = 100,
225
285
  ): void {
226
- schemaInitializers.push({ initialize, priority });
227
- schemaInitializers.sort((a, b) => a.priority - b.priority);
286
+ const initializers = storageState().schemaInitializers;
287
+ initializers.push({ initialize, priority });
288
+ initializers.sort((a, b) => a.priority - b.priority);
228
289
  }
229
290
 
230
291
  export function ensureStorageSchemas(
231
292
  database: InternalStorageDatabase = resolveStorageDatabase(),
232
293
  ): void {
233
- if (initializingDatabases.has(database)) return;
234
- let initialized = initializedSchemas.get(database);
294
+ const state = storageState();
295
+ if (state.initializingDatabases.has(database)) return;
296
+ let initialized = state.initializedSchemas.get(database);
235
297
  if (!initialized) {
236
298
  initialized = new Set();
237
- initializedSchemas.set(database, initialized);
299
+ state.initializedSchemas.set(database, initialized);
238
300
  }
239
- initializingDatabases.add(database);
301
+ state.initializingDatabases.add(database);
240
302
  try {
241
- for (const entry of schemaInitializers) {
303
+ for (const entry of state.schemaInitializers) {
242
304
  if (initialized.has(entry.initialize)) continue;
243
305
  // Mark first so a migration that calls through the public db proxy does
244
306
  // not recursively invoke itself. Remove on failure so the next call can retry.
@@ -251,7 +313,7 @@ export function ensureStorageSchemas(
251
313
  }
252
314
  }
253
315
  } finally {
254
- initializingDatabases.delete(database);
316
+ state.initializingDatabases.delete(database);
255
317
  }
256
318
  }
257
319
 
@@ -1,7 +1,8 @@
1
1
  import { chmodSync, mkdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { DATA_DIR, VAULT_MASTER_KEY } from "#platform/config";
3
+ import { VAULT_MASTER_KEY } from "#platform/config";
4
4
  import { Database } from "#storage/sqlite";
5
+ import { resolveStorageDataDir } from "#storage/storage-host";
5
6
  import { decryptVaultValue, encryptVaultValue } from "#storage/vault-crypto";
6
7
 
7
8
  export type VaultDatabase = Pick<InstanceType<typeof Database>, "exec" | "prepare">;
@@ -72,7 +73,14 @@ function openVaultDatabase(dataDir: string): VaultDatabase {
72
73
  }
73
74
 
74
75
  function activeVaultDatabase(): VaultDatabase {
75
- if (!vaultDb) vaultDb = openVaultDatabase(DATA_DIR);
76
+ if (!vaultDb) {
77
+ // `resolveStorageDataDir()`, not the import-time `DATA_DIR` constant: an
78
+ // embedding host configures its data directory during bootstrap, and any
79
+ // vault access that happens before `configureVaultStorage()` runs would
80
+ // otherwise pin the vault to Negotium's own state directory for the life of
81
+ // the process — silently, since both paths are valid and writable.
82
+ vaultDb = openVaultDatabase(resolveStorageDataDir());
83
+ }
76
84
  return vaultDb;
77
85
  }
78
86
 
@@ -83,7 +91,8 @@ export function configureVaultStorage(options: VaultStorageOptions): () => void
83
91
  }
84
92
  const previousDb = vaultDb;
85
93
  const previousMasterKey = vaultMasterKey;
86
- const configuredDb = options.database ?? openVaultDatabase(options.dataDir ?? DATA_DIR);
94
+ const configuredDb =
95
+ options.database ?? openVaultDatabase(options.dataDir ?? resolveStorageDataDir());
87
96
  if (options.database) initializeVaultDatabase(configuredDb);
88
97
  vaultDb = configuredDb;
89
98
  vaultMasterKey = options.masterKey ?? VAULT_MASTER_KEY;
@@ -1 +1 @@
1
- export const NEGOTIUM_VERSION = "0.2.19";
1
+ export const NEGOTIUM_VERSION = "0.2.21";
@@ -1,4 +1,20 @@
1
1
  // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
2
18
  var __require = import.meta.require;
3
19
 
4
20
  // ../../packages/core/src/agents/deep-map.ts
@@ -534,11 +550,12 @@ function versionAtLeast(actualVersion, minimumVersion) {
534
550
  }
535
551
  return true;
536
552
  }
553
+ var BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000;
537
554
  function browserRsMeetsMinimumVersion(candidate) {
538
555
  try {
539
556
  const output = execFileSync(candidate, ["--version"], {
540
557
  encoding: "utf8",
541
- timeout: 2000,
558
+ timeout: BROWSER_RS_VERSION_PROBE_TIMEOUT_MS,
542
559
  stdio: ["ignore", "pipe", "ignore"]
543
560
  }).trim();
544
561
  const match = output.match(/^browser-rs (\d+)\.(\d+)\.(\d+)$/);
@@ -764,12 +781,29 @@ if (isBun) {
764
781
  }
765
782
 
766
783
  // ../../packages/core/src/storage/storage-host.ts
767
- var configuredHost = {};
768
- var fallbackDatabase = null;
769
- var fallbackDatabasePath = null;
770
- var schemaInitializers = [];
771
- var initializedSchemas = new WeakMap;
772
- var initializingDatabases = new WeakSet;
784
+ var STORAGE_HOST_STATE = Symbol.for("negotium.storage-host.state.v1");
785
+ function storageState() {
786
+ const holder = globalThis;
787
+ const existing = holder[STORAGE_HOST_STATE];
788
+ if (existing)
789
+ return existing;
790
+ const created = {
791
+ configuredHost: {},
792
+ fallbackDatabase: null,
793
+ fallbackDatabasePath: null,
794
+ frames: [],
795
+ schemaInitializers: [],
796
+ initializedSchemas: new WeakMap,
797
+ initializingDatabases: new WeakSet
798
+ };
799
+ Object.defineProperty(holder, STORAGE_HOST_STATE, {
800
+ value: created,
801
+ enumerable: false,
802
+ writable: false,
803
+ configurable: false
804
+ });
805
+ return created;
806
+ }
773
807
  function envPath(name, fallback) {
774
808
  const value = process.env[name]?.trim();
775
809
  return resolve2(value || fallback);
@@ -814,37 +848,40 @@ function initializeDatabase(database) {
814
848
  }
815
849
  function defaultDatabase() {
816
850
  const path = defaultSessionsDatabasePath();
817
- if (fallbackDatabase && fallbackDatabasePath === path)
818
- return fallbackDatabase;
819
- if (fallbackDatabase)
820
- fallbackDatabase.close();
851
+ const state = storageState();
852
+ if (state.fallbackDatabase && state.fallbackDatabasePath === path)
853
+ return state.fallbackDatabase;
854
+ if (state.fallbackDatabase)
855
+ state.fallbackDatabase.close();
821
856
  mkdirSync4(dirname3(path), { recursive: true });
822
- fallbackDatabase = new Database(path, { create: true });
823
- fallbackDatabasePath = path;
824
- initializeDatabase(fallbackDatabase);
825
- return fallbackDatabase;
857
+ state.fallbackDatabase = new Database(path, { create: true });
858
+ state.fallbackDatabasePath = path;
859
+ initializeDatabase(state.fallbackDatabase);
860
+ return state.fallbackDatabase;
826
861
  }
827
862
  function resolveStorageDatabase() {
828
- return configuredHost.database ?? defaultDatabase();
863
+ return storageState().configuredHost.database ?? defaultDatabase();
829
864
  }
830
865
  function resolveStorageDataDir() {
831
- return configuredHost.dataDir ?? defaultDataDir();
866
+ return storageState().configuredHost.dataDir ?? defaultDataDir();
832
867
  }
833
868
  function registerStorageSchemaInitializer(initialize, priority = 100) {
834
- schemaInitializers.push({ initialize, priority });
835
- schemaInitializers.sort((a, b) => a.priority - b.priority);
869
+ const initializers = storageState().schemaInitializers;
870
+ initializers.push({ initialize, priority });
871
+ initializers.sort((a, b) => a.priority - b.priority);
836
872
  }
837
873
  function ensureStorageSchemas(database = resolveStorageDatabase()) {
838
- if (initializingDatabases.has(database))
874
+ const state = storageState();
875
+ if (state.initializingDatabases.has(database))
839
876
  return;
840
- let initialized = initializedSchemas.get(database);
877
+ let initialized = state.initializedSchemas.get(database);
841
878
  if (!initialized) {
842
879
  initialized = new Set;
843
- initializedSchemas.set(database, initialized);
880
+ state.initializedSchemas.set(database, initialized);
844
881
  }
845
- initializingDatabases.add(database);
882
+ state.initializingDatabases.add(database);
846
883
  try {
847
- for (const entry of schemaInitializers) {
884
+ for (const entry of state.schemaInitializers) {
848
885
  if (initialized.has(entry.initialize))
849
886
  continue;
850
887
  initialized.add(entry.initialize);
@@ -856,7 +893,7 @@ function ensureStorageSchemas(database = resolveStorageDatabase()) {
856
893
  }
857
894
  }
858
895
  } finally {
859
- initializingDatabases.delete(database);
896
+ state.initializingDatabases.delete(database);
860
897
  }
861
898
  }
862
899
  var internalStorageDatabase = new Proxy({}, {
@@ -1558,4 +1595,4 @@ export {
1558
1595
  CLAUDE_EFFORT_VALUES
1559
1596
  };
1560
1597
 
1561
- //# debugId=A7EE5EAA6DA08CC064756E2164756E21
1598
+ //# debugId=6B371802D724F43F64756E2164756E21