@steerable/agent-shell 0.6.20 → 0.6.22

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 (64) hide show
  1. package/README.md +34 -0
  2. package/dist/auth/index.d.ts +38 -0
  3. package/dist/auth/index.js +20 -0
  4. package/dist/host/pack-assembly.d.ts +7 -7
  5. package/dist/host/runtime.d.ts +6 -2
  6. package/dist/host/runtime.js +54 -15
  7. package/dist/host/visible-terminal-exec.d.ts +0 -2
  8. package/dist/host/visible-terminal-exec.js +4 -8
  9. package/dist/insights/flush.d.ts +5 -4
  10. package/dist/insights/flush.js +14 -15
  11. package/dist/insights/record.d.ts +6 -5
  12. package/dist/insights/record.js +9 -10
  13. package/dist/llm/index.d.ts +6 -2
  14. package/dist/llm/index.js +19 -9
  15. package/dist/local-backend/ai-suggestions.d.ts +32 -0
  16. package/dist/local-backend/ai-suggestions.js +160 -0
  17. package/dist/local-backend/coreloop-stream.js +1 -1
  18. package/dist/local-backend/mentioned-paths.d.ts +28 -0
  19. package/dist/local-backend/mentioned-paths.js +67 -0
  20. package/dist/local-backend/prompt-builder.d.ts +10 -4
  21. package/dist/local-backend/prompt-builder.js +13 -12
  22. package/dist/local-backend/router.d.ts +26 -4
  23. package/dist/local-backend/router.js +243 -96
  24. package/dist/local-backend/skills/00-identity/SKILL.md +1 -1
  25. package/dist/local-backend/task-service.d.ts +14 -13
  26. package/dist/local-backend/task-service.js +32 -29
  27. package/dist/local-backend/turn-files.d.ts +57 -0
  28. package/dist/local-backend/turn-files.js +285 -0
  29. package/dist/local-backend/worktree-service.d.ts +1 -1
  30. package/dist/local-backend/worktree-service.js +6 -6
  31. package/dist/main.js +45 -26
  32. package/dist/preload.cjs +30 -10
  33. package/dist/preload.js +33 -10
  34. package/dist/server/http-server.d.ts +13 -1
  35. package/dist/server/http-server.js +45 -8
  36. package/dist/server/index.js +4 -90
  37. package/dist/server/sse-bus.d.ts +1 -1
  38. package/dist/server/start.d.ts +26 -0
  39. package/dist/server/start.js +111 -0
  40. package/dist/sidecar/boot.d.ts +3 -1
  41. package/dist/sidecar/boot.js +6 -7
  42. package/dist/sidecar/reverse-approval.d.ts +2 -0
  43. package/dist/sidecar/reverse-approval.js +2 -1
  44. package/dist/sidecar/reverse-ask-user.d.ts +2 -0
  45. package/dist/sidecar/reverse-ask-user.js +2 -1
  46. package/dist/sidecar/reverse-spawn.js +12 -4
  47. package/dist/sidecar/reverse-tools.d.ts +1 -1
  48. package/dist/sidecar/reverse-tools.js +1 -1
  49. package/dist/sidecar/storage-path.js +2 -3
  50. package/dist/storage/driver.d.ts +57 -0
  51. package/dist/storage/driver.js +104 -0
  52. package/dist/storage/index.d.ts +80 -67
  53. package/dist/storage/index.js +528 -296
  54. package/dist/storage/pack-migrations.d.ts +2 -4
  55. package/dist/storage/pack-migrations.js +2 -4
  56. package/dist/storage/scoped-store.d.ts +125 -0
  57. package/dist/storage/scoped-store.js +1 -0
  58. package/dist/storage/sqlite-driver.d.ts +16 -0
  59. package/dist/storage/sqlite-driver.js +135 -0
  60. package/dist/storage/write-lease-error.d.ts +11 -0
  61. package/dist/storage/write-lease-error.js +18 -0
  62. package/package.json +14 -5
  63. package/dist/storage/local-store-singleton.d.ts +0 -28
  64. package/dist/storage/local-store-singleton.js +0 -38
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Composable BS host assembly for executable and embedded deployments.
3
+ */
4
+ import { existsSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import dotenv from 'dotenv';
8
+ import log from 'electron-log';
9
+ import { getBrand } from '../brand.js';
10
+ import { registerPackHttpRoutes } from '../host/http-routes.js';
11
+ import { createHostRuntime } from '../host/runtime.js';
12
+ import { getUserDataDir } from '../runtime.js';
13
+ import { createBsServer, } from './http-server.js';
14
+ import { SseBus } from './sse-bus.js';
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ // Match the Electron entry's dotenv precedence: local file first, existing
17
+ // process variables always win.
18
+ for (const file of ['.env.local', '.env']) {
19
+ const fullPath = path.join(path.resolve(__dirname, '..', '..'), file);
20
+ if (existsSync(fullPath))
21
+ dotenv.config({ path: fullPath, override: false });
22
+ }
23
+ log.transports.file.resolvePathFn = () => path.join(getUserDataDir(), 'logs', 'main.log');
24
+ /**
25
+ * Assembles and starts the BS HTTP host.
26
+ *
27
+ * Product composition roots may register providers, package contributions,
28
+ * and middleware before calling this function.
29
+ */
30
+ export async function startBsHost(options = {}) {
31
+ const brand = getBrand();
32
+ const bus = new SseBus();
33
+ const broadcast = (channel, payload) => bus.broadcast(channel, payload);
34
+ const runtime = await createHostRuntime({
35
+ scope: options.scope,
36
+ broadcast,
37
+ hasWindow: () => bus.size > 0,
38
+ onLog: (line) => log.info('[sidecar]', line),
39
+ taskSweepReason: '服务重启,任务流已中断',
40
+ });
41
+ const { localExecutor, localScriptRegistry, terminalManager, packHandles, localBackendRouter, approvalBridge, askUserBridge, maybeExecInTerminal, } = runtime;
42
+ const webDistDir = process.env.DEEPPATH_WEB_DIST ??
43
+ path.join(__dirname, '..', '..', 'web', 'dist');
44
+ if (!existsSync(path.join(webDistDir, 'index.html'))) {
45
+ await runtime.shutdown();
46
+ throw new Error(`[bs] web build not found at ${webDistDir} — run \`pnpm build\` first.`);
47
+ }
48
+ for (const [packId, handle] of packHandles) {
49
+ const routes = handle.httpRoutes?.();
50
+ if (routes)
51
+ registerPackHttpRoutes(packId, routes);
52
+ }
53
+ const server = createBsServer({
54
+ store: runtime.store,
55
+ localBackendRouter,
56
+ localExecutor,
57
+ localScriptRegistry,
58
+ terminalManager,
59
+ approvalBridge,
60
+ askUserBridge,
61
+ maybeExecInTerminal,
62
+ bus,
63
+ webDistDir,
64
+ authToken: options.authToken,
65
+ middleware: options.middleware,
66
+ });
67
+ const host = options.host ?? (process.env.DEEPPATH_BS_HOST || '127.0.0.1');
68
+ const requestedPort = options.port ?? Number(process.env.DEEPPATH_BS_PORT || 4787);
69
+ try {
70
+ await new Promise((resolve, reject) => {
71
+ const onError = (error) => reject(error);
72
+ server.once('error', onError);
73
+ server.listen(requestedPort, host, () => {
74
+ server.off('error', onError);
75
+ resolve();
76
+ });
77
+ });
78
+ }
79
+ catch (error) {
80
+ await runtime.shutdown();
81
+ throw error;
82
+ }
83
+ const address = server.address();
84
+ const port = address.port;
85
+ console.log(`[bs] ${brand.displayName} server listening at http://${host}:${port} (flavor=${brand.flavor})`);
86
+ await runtime.start();
87
+ let shutdownPromise = null;
88
+ return {
89
+ server,
90
+ host,
91
+ port,
92
+ shutdown() {
93
+ shutdownPromise ??= (async () => {
94
+ try {
95
+ await new Promise((resolve, reject) => {
96
+ server.close((error) => {
97
+ if (error)
98
+ reject(error);
99
+ else
100
+ resolve();
101
+ });
102
+ });
103
+ }
104
+ finally {
105
+ await runtime.shutdown();
106
+ }
107
+ })();
108
+ return shutdownPromise;
109
+ },
110
+ };
111
+ }
@@ -1,10 +1,12 @@
1
+ import type { ScopedStore } from '../storage/scoped-store.js';
1
2
  import { type ToolRouter } from '../tool-router.js';
2
3
  import type { createApprovalBridge } from './reverse-approval.js';
3
4
  import type { SidecarReverseHandler } from './types.js';
4
5
  export interface HostSidecarDeps {
6
+ store: ScopedStore;
5
7
  toolRouter: ToolRouter;
6
8
  /** W4-2 项目模式围栏:chatId → 项目根(无项目对话返回 null)。 */
7
- resolveProjectRoot: (chatId: string) => string | null;
9
+ resolveProjectRoot: (chatId: string) => Promise<string | null>;
8
10
  /**
9
11
  * 项目模式之外额外放行的只读根(会话附件目录等)。文件写入仍只受
10
12
  * projectRoot 围栏约束;这些根只放宽 local_read_file 的读取范围。
@@ -10,7 +10,6 @@ import path from 'node:path';
10
10
  import log from 'electron-log';
11
11
  import { SidecarSupervisor, resolveSidecarPython } from './supervisor.js';
12
12
  import { setSidecarSupervisor, setSidecarSupervisorPending, llmService } from '../llm/index.js';
13
- import { localStore } from '../storage/index.js';
14
13
  import { resolveSidecarStoragePath } from './storage-path.js';
15
14
  import { deriveEgressAllowListFromBaseUrl, probeExecSandboxCapability, } from './exec-sandbox.js';
16
15
  import { createToolInvokeHandler } from './reverse-tools.js';
@@ -58,7 +57,7 @@ async function deriveSidecarEgressAllowList() {
58
57
  * ambient 端点并进允许列表,见 W4-8)。
59
58
  * - 启动失败 / 派生不出任何代理条目。
60
59
  */
61
- async function startEgressProxyIfEnabled() {
60
+ async function startEgressProxyIfEnabled(store) {
62
61
  try {
63
62
  const ambient = process.env.STEERABLE_EGRESS_PROXY === '0' ? [] : await collectAmbientProxyEndpoints();
64
63
  const decision = decideEgressProxy({ env: process.env, ambientProxies: ambient });
@@ -72,7 +71,7 @@ async function startEgressProxyIfEnabled() {
72
71
  return null;
73
72
  }
74
73
  const settings = llmService.getSettings();
75
- const searchSettings = localStore.getWebSearchSettings();
74
+ const searchSettings = await store.getWebSearchSettings();
76
75
  const searchEnv = sidecarWebSearchEnv({
77
76
  processEnv: process.env,
78
77
  storedApiKey: searchSettings?.apiKey,
@@ -214,7 +213,7 @@ export async function startHostSidecar(deps) {
214
213
  consentGranted: true,
215
214
  timeoutMs: WEB_TOOL_RPC_TIMEOUT_MS,
216
215
  }), webNames);
217
- const searchSettings = localStore.getWebSearchSettings();
216
+ const searchSettings = await deps.store.getWebSearchSettings();
218
217
  const searchEnv = sidecarWebSearchEnv({
219
218
  processEnv: process.env,
220
219
  storedApiKey: searchSettings?.apiKey,
@@ -254,7 +253,7 @@ export async function startHostSidecar(deps) {
254
253
  };
255
254
  const boot = (async () => {
256
255
  try {
257
- const egressProxy = await startEgressProxyIfEnabled();
256
+ const egressProxy = await startEgressProxyIfEnabled(deps.store);
258
257
  // W2.6.1: durable sessions/traces/history in a zero-dependency
259
258
  // sqlite database under ~/.steerable — the one root the Seatbelt
260
259
  // profile already allows writes to; userData would be denied.
@@ -286,8 +285,8 @@ export async function startHostSidecar(deps) {
286
285
  ...egressProxy?.env,
287
286
  ...sidecarWebSearchEnv({
288
287
  processEnv: process.env,
289
- storedApiKey: localStore.getWebSearchSettings()?.apiKey,
290
- storedProvider: localStore.getWebSearchSettings()?.provider,
288
+ storedApiKey: (await deps.store.getWebSearchSettings())?.apiKey,
289
+ storedProvider: (await deps.store.getWebSearchSettings())?.provider,
291
290
  llmBaseUrl: llmService.getSettings().baseUrl,
292
291
  }),
293
292
  // P1: offer the sidecar's run_code (programmatic tool calls) to the
@@ -43,6 +43,8 @@ export interface ApprovalBridgeDeps {
43
43
  export interface ApprovalBridge {
44
44
  /** Reverse-channel handler for the sidecar's `approval.request`. */
45
45
  handler: SidecarReverseHandler;
46
+ /** Requests still awaiting a renderer decision, used after page refresh. */
47
+ pending: () => ApprovalPromptRequest[];
46
48
  /**
47
49
  * IPC entry point for the renderer's answer (`ipcMain.handle(
48
50
  * 'approval:decide')`). Unknown requestIds are dropped (late answer to an
@@ -54,7 +54,7 @@ export function createApprovalBridge(deps) {
54
54
  };
55
55
  deps.onLog?.(`approval: prompting for ${toolName} (${prompt.mode}/${prompt.category})`);
56
56
  return await new Promise((resolve) => {
57
- pending.set(requestId, { resolve });
57
+ pending.set(requestId, { prompt, resolve });
58
58
  try {
59
59
  deps.broadcast('approval:request', prompt);
60
60
  }
@@ -64,6 +64,7 @@ export function createApprovalBridge(deps) {
64
64
  }
65
65
  });
66
66
  },
67
+ pending: () => Array.from(pending.values(), ({ prompt }) => prompt),
67
68
  decide: (payload) => {
68
69
  const p = (payload ?? {});
69
70
  const requestId = typeof p.requestId === 'string' ? p.requestId : '';
@@ -26,6 +26,8 @@ export interface AskUserBridgeDeps {
26
26
  export interface AskUserBridge {
27
27
  /** 注册为 sidecar 的 `ask_user.request` 反向方法处理器。 */
28
28
  handler: SidecarReverseHandler;
29
+ /** 返回仍在等待 renderer 应答的请求,供刷新后的页面恢复提问。 */
30
+ pending: () => AskUserPromptRequest[];
29
31
  /** renderer `ask-user:answer` 的入口;返回是否匹配到待应答请求。 */
30
32
  answer: (payload: unknown) => {
31
33
  ok: boolean;
@@ -32,7 +32,7 @@ export function createAskUserBridge(deps) {
32
32
  questions: questions,
33
33
  };
34
34
  return new Promise((resolve) => {
35
- pending.set(requestId, { resolve });
35
+ pending.set(requestId, { prompt, resolve });
36
36
  try {
37
37
  deps.broadcast('ask-user:request', prompt);
38
38
  }
@@ -43,6 +43,7 @@ export function createAskUserBridge(deps) {
43
43
  }
44
44
  });
45
45
  },
46
+ pending: () => Array.from(pending.values(), ({ prompt }) => prompt),
46
47
  answer: (payload) => {
47
48
  const p = (payload ?? {});
48
49
  const entry = typeof p.requestId === 'string' ? pending.get(p.requestId) : undefined;
@@ -29,17 +29,25 @@ export function resolveWinSpawnHelperPath() {
29
29
  // 显式覆盖(测试/调试逃生门):设置后只认这一条路径。指向不存在的
30
30
  // 路径即可强制走"helper 缺失"的失败分支,与 dev checkout 是否已构建
31
31
  // Rust 二进制无关,保证失败分支的测试确定性。
32
- const override = process.env.DEEPPATH_WIN_SPAWN_HELPER;
32
+ const override = process.env.DEEPPATH_WIN_SPAWN_HELPER ?? process.env.STEERABLE_WIN_SPAWN_HELPER;
33
33
  if (override !== undefined) {
34
34
  return override && existsSync(override) ? override : null;
35
35
  }
36
- const resourcesPath = process.resourcesPath;
36
+ const electron = process;
37
+ const resourcesPath = electron.resourcesPath;
37
38
  const candidates = [];
38
39
  if (resourcesPath) {
39
40
  candidates.push(join(resourcesPath, 'win-spawn-helper', binary));
40
41
  }
41
- // Dev checkout: native/ builds into resources/windows-spawn-helper/.
42
- candidates.push(join(__dirname, '..', '..', 'resources', 'windows-spawn-helper', binary));
42
+ // Packaged extraResources live under resourcesPath. cwd is the user's
43
+ // launch directory and must not supply the confinement binary.
44
+ // `electron .` sets defaultApp and does not copy extraResources there;
45
+ // npm-published shell also cannot see the host checkout via __dirname.
46
+ const packagedElectron = Boolean(resourcesPath) && electron.defaultApp !== true;
47
+ if (!packagedElectron) {
48
+ candidates.push(join(process.cwd(), 'resources', 'windows-spawn-helper', binary));
49
+ candidates.push(join(__dirname, '..', '..', 'resources', 'windows-spawn-helper', binary));
50
+ }
43
51
  for (const candidate of candidates) {
44
52
  if (existsSync(candidate))
45
53
  return candidate;
@@ -19,7 +19,7 @@ export interface ReverseToolDeps {
19
19
  * main.ts 注入 LocalBackendRouter.resolveChatProject 的适配;未注入时
20
20
  * 项目围栏不生效(CLI/test 场景)。
21
21
  */
22
- resolveProjectRoot?: (chatId: string) => string | null;
22
+ resolveProjectRoot?: (chatId: string) => Promise<string | null>;
23
23
  /**
24
24
  * 额外放行的只读根(会话附件目录等)。写入仍只受 projectRoot 围栏约束,
25
25
  * 这里只放宽 local_read_file 的读取范围。
@@ -53,7 +53,7 @@ export function createToolInvokeHandler(deps) {
53
53
  const projectRoot = typeof p.context?.workspaceRoot === 'string' && p.context.workspaceRoot
54
54
  ? p.context.workspaceRoot
55
55
  : p.context?.chatId
56
- ? (deps.resolveProjectRoot?.(p.context.chatId) ?? null)
56
+ ? (await deps.resolveProjectRoot?.(p.context.chatId) ?? null)
57
57
  : null;
58
58
  const additionalReadRoots = p.context?.chatId
59
59
  ? (deps.resolveAdditionalReadRoots?.(p.context.chatId) ?? [])
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Sidecar 持久化库(sessions/traces/history)路径解析。
3
3
  *
4
- * 单独成模块而不是留在 boot.ts:boot.ts 会拉起 localStore 单例(进程内
5
- * 独占写锁),只想读 history 的调用方(如后台任务过程投影)不该为一个
6
- * 路径付这个代价。
4
+ * Kept outside boot.ts so history readers can resolve this separate database
5
+ * without initializing host storage.
7
6
  */
8
7
  import path from 'node:path';
9
8
  import { homedir } from 'node:os';
@@ -0,0 +1,57 @@
1
+ import type { ScopedStore } from './scoped-store.js';
2
+ /** Ownership context applied to every host and scenario-pack database operation. */
3
+ export interface TenantScope {
4
+ tenantId: string;
5
+ userId: string;
6
+ }
7
+ /** Ownership used by the personal, single-user product. */
8
+ export declare const LOCAL_SCOPE: TenantScope;
9
+ export type PackDbParams = readonly unknown[] | Readonly<Record<string, unknown>>;
10
+ export interface PackDbRunResult {
11
+ changes: number;
12
+ lastInsertRowid: number | bigint;
13
+ }
14
+ /**
15
+ * Driver-neutral SQL access for scenario-pack repositories.
16
+ *
17
+ * The access object carries scope but cannot safely rewrite arbitrary SQL.
18
+ * Every pack statement must include tenant_id and user_id predicates or values;
19
+ * drivers and source gates reject missing or mismatched ownership. The driver
20
+ * connection is never exposed.
21
+ */
22
+ export interface PackDbAccess {
23
+ readonly scope: TenantScope;
24
+ get<T extends Record<string, unknown>>(sql: string, params?: PackDbParams): Promise<T | undefined>;
25
+ all<T extends Record<string, unknown>>(sql: string, params?: PackDbParams): Promise<T[]>;
26
+ run(sql: string, params?: PackDbParams): Promise<PackDbRunResult>;
27
+ exec(sql: string): Promise<void>;
28
+ transaction<T>(operation: (db: PackDbAccess) => Promise<T>): Promise<T>;
29
+ }
30
+ export type { ScopedStore } from './scoped-store.js';
31
+ /** Product-selectable host storage implementation. */
32
+ export interface StorageDriver {
33
+ initialize(): Promise<void>;
34
+ scoped(scope: TenantScope): ScopedStore;
35
+ packAccess(scope: TenantScope): PackDbAccess;
36
+ applyPackMigrations(): Promise<void>;
37
+ close(): Promise<void>;
38
+ }
39
+ export type StorageDriverFactory = () => StorageDriver | Promise<StorageDriver>;
40
+ /** Registers a storage driver. Product packs call this during composition. */
41
+ export declare function registerStorageDriver(id: string, factory: StorageDriverFactory, options?: {
42
+ default?: boolean;
43
+ }): () => void;
44
+ /** Selects a registered driver before storage initialization. */
45
+ export declare function selectStorageDriver(id: string): void;
46
+ /**
47
+ * Initializes storage after all product-pack registrations have run.
48
+ *
49
+ * Calls are idempotent and share one initialization promise.
50
+ */
51
+ export declare function initializeStorage(): Promise<StorageDriver>;
52
+ /** Returns a scoped view after explicit storage initialization. */
53
+ export declare function getScopedStore(scope: TenantScope): ScopedStore;
54
+ /** Returns scope-bound scenario-pack access after initialization. */
55
+ export declare function getPackDbAccess(scope: TenantScope): PackDbAccess;
56
+ /** Closes the active driver. Mainly used by host shutdown and tests. */
57
+ export declare function closeStorage(): Promise<void>;
@@ -0,0 +1,104 @@
1
+ /** Ownership used by the personal, single-user product. */
2
+ export const LOCAL_SCOPE = Object.freeze({
3
+ tenantId: 'local',
4
+ userId: 'local',
5
+ });
6
+ const DEFAULT_DRIVER_ID = 'sqlite';
7
+ const factories = new Map();
8
+ let selectedDriverId = DEFAULT_DRIVER_ID;
9
+ let activeDriver = null;
10
+ let initialization = null;
11
+ /** Registers a storage driver. Product packs call this during composition. */
12
+ export function registerStorageDriver(id, factory, options = {}) {
13
+ if (factories.has(id)) {
14
+ throw new Error(`[storage] duplicate driver registration: ${id}`);
15
+ }
16
+ if (activeDriver || initialization) {
17
+ throw new Error('[storage] drivers must be registered before initialization');
18
+ }
19
+ factories.set(id, factory);
20
+ if (options.default)
21
+ selectedDriverId = id;
22
+ return () => {
23
+ if (!activeDriver && !initialization) {
24
+ factories.delete(id);
25
+ if (selectedDriverId === id)
26
+ selectedDriverId = DEFAULT_DRIVER_ID;
27
+ }
28
+ };
29
+ }
30
+ /** Selects a registered driver before storage initialization. */
31
+ export function selectStorageDriver(id) {
32
+ if (activeDriver || initialization) {
33
+ throw new Error('[storage] driver selection is closed after initialization');
34
+ }
35
+ selectedDriverId = id;
36
+ }
37
+ /**
38
+ * Initializes storage after all product-pack registrations have run.
39
+ *
40
+ * Calls are idempotent and share one initialization promise.
41
+ */
42
+ export function initializeStorage() {
43
+ if (activeDriver)
44
+ return Promise.resolve(activeDriver);
45
+ if (initialization)
46
+ return initialization;
47
+ const attempt = (async () => {
48
+ const factory = factories.get(selectedDriverId);
49
+ if (!factory) {
50
+ throw new Error(`[storage] unknown storage driver: ${selectedDriverId}`);
51
+ }
52
+ let driver = null;
53
+ try {
54
+ driver = await factory();
55
+ await driver.initialize();
56
+ await driver.applyPackMigrations();
57
+ activeDriver = driver;
58
+ return driver;
59
+ }
60
+ catch (error) {
61
+ if (driver) {
62
+ try {
63
+ await driver.close();
64
+ }
65
+ catch (_closeError) {
66
+ // Initialization error remains the actionable startup failure.
67
+ }
68
+ }
69
+ throw error;
70
+ }
71
+ })();
72
+ initialization = attempt;
73
+ void attempt.catch(() => {
74
+ if (initialization === attempt)
75
+ initialization = null;
76
+ });
77
+ return attempt;
78
+ }
79
+ /** Returns a scoped view after explicit storage initialization. */
80
+ export function getScopedStore(scope) {
81
+ if (!activeDriver) {
82
+ throw new Error('[storage] initializeStorage() must complete before use');
83
+ }
84
+ return activeDriver.scoped(scope);
85
+ }
86
+ /** Returns scope-bound scenario-pack access after initialization. */
87
+ export function getPackDbAccess(scope) {
88
+ if (!activeDriver) {
89
+ throw new Error('[storage] initializeStorage() must complete before use');
90
+ }
91
+ return activeDriver.packAccess(scope);
92
+ }
93
+ /** Closes the active driver. Mainly used by host shutdown and tests. */
94
+ export async function closeStorage() {
95
+ const driver = activeDriver;
96
+ activeDriver = null;
97
+ initialization = null;
98
+ if (driver)
99
+ await driver.close();
100
+ }
101
+ registerStorageDriver(DEFAULT_DRIVER_ID, async () => {
102
+ const { SqliteStorageDriver } = await import('./sqlite-driver.js');
103
+ return new SqliteStorageDriver();
104
+ });