@sna-sdk/core 0.9.10 → 0.9.11

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.
@@ -1,5 +1,7 @@
1
1
  import { ChildProcess } from 'child_process';
2
+ import http from 'http';
2
3
  export { ResolveResult, cacheClaudePath, parseCommandVOutput, resolveClaudeCli, validateClaudePath } from '../core/providers/claude-code.js';
4
+ import { SessionManager } from '../server/session-manager.js';
3
5
  import '../core/providers/types.js';
4
6
 
5
7
  /**
@@ -102,5 +104,33 @@ interface SnaServerHandle {
102
104
  * Throws if the server fails to start within `options.readyTimeout`.
103
105
  */
104
106
  declare function startSnaServer(options: SnaServerOptions): Promise<SnaServerHandle>;
107
+ interface InProcessSnaServerHandle {
108
+ /** No child process — the server runs in the calling process. */
109
+ process: null;
110
+ /** The port the server is listening on. */
111
+ port: number;
112
+ /** The session manager instance. */
113
+ sessionManager: SessionManager;
114
+ /** The underlying HTTP server. */
115
+ httpServer: http.Server;
116
+ /** Graceful shutdown: kill all sessions and close the HTTP server. */
117
+ stop(): Promise<void>;
118
+ }
119
+ /**
120
+ * Launch the SNA API server **in-process** (no fork).
121
+ *
122
+ * Designed for Electron main processes where fork() causes problems:
123
+ * - asar module resolution failures
124
+ * - PATH / env propagation issues
125
+ * - orphaned child processes on crash
126
+ *
127
+ * The server runs on the same Node.js event loop as the caller. Use `stop()`
128
+ * to tear down cleanly (e.g., in Electron's `before-quit` handler).
129
+ *
130
+ * Unlike fork mode, this does **not** spawn a default agent — the consumer
131
+ * controls session/agent lifecycle via the returned `sessionManager` or via
132
+ * the WebSocket / HTTP API.
133
+ */
134
+ declare function startSnaServerInProcess(options: SnaServerOptions): Promise<InProcessSnaServerHandle>;
105
135
 
106
- export { type SnaServerHandle, type SnaServerOptions, startSnaServer };
136
+ export { type InProcessSnaServerHandle, type SnaServerHandle, type SnaServerOptions, startSnaServer, startSnaServerInProcess };
@@ -3,6 +3,14 @@ import { fileURLToPath } from "url";
3
3
  import fs from "fs";
4
4
  import { resolveClaudeCli, validateClaudePath, cacheClaudePath, parseCommandVOutput } from "../core/providers/claude-code.js";
5
5
  import path from "path";
6
+ import { Hono } from "hono";
7
+ import { cors } from "hono/cors";
8
+ import { serve } from "@hono/node-server";
9
+ import { createSnaApp } from "../server/index.js";
10
+ import { SessionManager } from "../server/session-manager.js";
11
+ import { attachWebSocket } from "../server/ws.js";
12
+ import { setConfig, getConfig } from "../config.js";
13
+ import { getDb } from "../db/schema.js";
6
14
  function resolveStandaloneScript() {
7
15
  const selfPath = fileURLToPath(import.meta.url);
8
16
  let script = path.resolve(path.dirname(selfPath), "../server/standalone.js");
@@ -107,10 +115,82 @@ async function startSnaServer(options) {
107
115
  }
108
116
  };
109
117
  }
118
+ async function startSnaServerInProcess(options) {
119
+ const port = options.port ?? 3099;
120
+ const cwd = options.cwd ?? path.dirname(options.dbPath);
121
+ setConfig({
122
+ port,
123
+ dbPath: options.dbPath,
124
+ ...options.maxSessions != null ? { maxSessions: options.maxSessions } : {},
125
+ ...options.permissionMode ? { defaultPermissionMode: options.permissionMode } : {},
126
+ ...options.model ? { model: options.model } : {},
127
+ ...options.permissionTimeoutMs != null ? { permissionTimeoutMs: options.permissionTimeoutMs } : {}
128
+ });
129
+ process.env.SNA_PORT = String(port);
130
+ process.env.SNA_DB_PATH = options.dbPath;
131
+ if (options.maxSessions != null) process.env.SNA_MAX_SESSIONS = String(options.maxSessions);
132
+ if (options.permissionMode) process.env.SNA_PERMISSION_MODE = options.permissionMode;
133
+ if (options.model) process.env.SNA_MODEL = options.model;
134
+ if (options.permissionTimeoutMs != null) process.env.SNA_PERMISSION_TIMEOUT_MS = String(options.permissionTimeoutMs);
135
+ if (options.nativeBinding) process.env.SNA_SQLITE_NATIVE_BINDING = options.nativeBinding;
136
+ if (!process.env.SNA_MODULES_PATH) {
137
+ try {
138
+ const bsPkg = require.resolve("better-sqlite3/package.json", { paths: [process.cwd()] });
139
+ process.env.SNA_MODULES_PATH = path.resolve(bsPkg, "../..");
140
+ } catch {
141
+ }
142
+ }
143
+ const originalCwd = process.cwd();
144
+ try {
145
+ process.chdir(cwd);
146
+ } catch {
147
+ }
148
+ try {
149
+ getDb();
150
+ } catch (err) {
151
+ process.chdir(originalCwd);
152
+ throw new Error(`SNA in-process: database init failed: ${err.message}`);
153
+ }
154
+ const config = getConfig();
155
+ const root = new Hono();
156
+ root.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"] }));
157
+ root.onError((err, c) => {
158
+ const pathname = new URL(c.req.url).pathname;
159
+ if (options.onLog) options.onLog(`ERR ${c.req.method} ${pathname} \u2192 ${err.message}`);
160
+ return c.json({ status: "error", message: err.message, stack: err.stack }, 500);
161
+ });
162
+ root.use("*", async (c, next) => {
163
+ const m = c.req.method;
164
+ const pathname = new URL(c.req.url).pathname;
165
+ if (options.onLog) options.onLog(`${m.padEnd(6)} ${pathname}`);
166
+ await next();
167
+ });
168
+ const sessionManager = new SessionManager({ maxSessions: config.maxSessions });
169
+ root.route("/", createSnaApp({ sessionManager }));
170
+ const httpServer = serve({ fetch: root.fetch, port }, () => {
171
+ if (options.onLog) options.onLog(`API server ready \u2192 http://localhost:${port}`);
172
+ if (options.onLog) options.onLog(`WebSocket endpoint \u2192 ws://localhost:${port}/ws`);
173
+ });
174
+ attachWebSocket(httpServer, sessionManager);
175
+ return {
176
+ process: null,
177
+ port,
178
+ sessionManager,
179
+ httpServer,
180
+ async stop() {
181
+ sessionManager.killAll();
182
+ await new Promise((resolve) => {
183
+ httpServer.close(() => resolve());
184
+ setTimeout(() => resolve(), 3e3).unref();
185
+ });
186
+ }
187
+ };
188
+ }
110
189
  export {
111
190
  cacheClaudePath,
112
191
  parseCommandVOutput,
113
192
  resolveClaudeCli,
114
193
  startSnaServer,
194
+ startSnaServerInProcess,
115
195
  validateClaudePath
116
196
  };
@@ -39,14 +39,72 @@ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${_
39
39
  var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
40
40
 
41
41
  // src/electron/index.ts
42
- var import_child_process = require("child_process");
43
- var import_url = require("url");
44
- var import_fs2 = __toESM(require("fs"), 1);
42
+ var import_child_process2 = require("child_process");
43
+ var import_url2 = require("url");
44
+ var import_fs4 = __toESM(require("fs"), 1);
45
45
 
46
46
  // src/core/providers/claude-code.ts
47
+ var import_child_process = require("child_process");
47
48
  var import_events = require("events");
49
+ var import_fs3 = __toESM(require("fs"), 1);
50
+ var import_path3 = __toESM(require("path"), 1);
51
+ var import_url = require("url");
48
52
 
49
53
  // src/core/providers/cc-history-adapter.ts
54
+ var import_fs = __toESM(require("fs"), 1);
55
+ var import_path = __toESM(require("path"), 1);
56
+ function writeHistoryJsonl(history, opts) {
57
+ for (let i = 1; i < history.length; i++) {
58
+ if (history[i].role === history[i - 1].role) {
59
+ throw new Error(
60
+ `History validation failed: consecutive ${history[i].role} at index ${i - 1} and ${i}. Messages must alternate user\u2194assistant. Merge tool results into text before injecting.`
61
+ );
62
+ }
63
+ }
64
+ try {
65
+ const dir = import_path.default.join(opts.cwd, ".sna", "history");
66
+ import_fs.default.mkdirSync(dir, { recursive: true });
67
+ const sessionId = crypto.randomUUID();
68
+ const filePath = import_path.default.join(dir, `${sessionId}.jsonl`);
69
+ const now = (/* @__PURE__ */ new Date()).toISOString();
70
+ const lines = [];
71
+ let prevUuid = null;
72
+ for (const msg of history) {
73
+ const uuid = crypto.randomUUID();
74
+ if (msg.role === "user") {
75
+ lines.push(JSON.stringify({
76
+ parentUuid: prevUuid,
77
+ isSidechain: false,
78
+ type: "user",
79
+ uuid,
80
+ timestamp: now,
81
+ cwd: opts.cwd,
82
+ sessionId,
83
+ message: { role: "user", content: msg.content }
84
+ }));
85
+ } else {
86
+ lines.push(JSON.stringify({
87
+ parentUuid: prevUuid,
88
+ isSidechain: false,
89
+ type: "assistant",
90
+ uuid,
91
+ timestamp: now,
92
+ cwd: opts.cwd,
93
+ sessionId,
94
+ message: {
95
+ role: "assistant",
96
+ content: [{ type: "text", text: msg.content }]
97
+ }
98
+ }));
99
+ }
100
+ prevUuid = uuid;
101
+ }
102
+ import_fs.default.writeFileSync(filePath, lines.join("\n") + "\n");
103
+ return { filePath, extraArgs: ["--resume", filePath] };
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
50
108
  function buildRecalledConversation(history) {
51
109
  const xml = history.map((msg) => `<${msg.role}>${msg.content}</${msg.role}>`).join("\n");
52
110
  return JSON.stringify({
@@ -61,11 +119,11 @@ ${xml}
61
119
  }
62
120
 
63
121
  // src/lib/logger.ts
64
- var import_fs = __toESM(require("fs"), 1);
65
- var import_path = __toESM(require("path"), 1);
66
- var LOG_PATH = import_path.default.join(process.cwd(), ".dev.log");
122
+ var import_fs2 = __toESM(require("fs"), 1);
123
+ var import_path2 = __toESM(require("path"), 1);
124
+ var LOG_PATH = import_path2.default.join(process.cwd(), ".dev.log");
67
125
  try {
68
- import_fs.default.writeFileSync(LOG_PATH, "");
126
+ import_fs2.default.writeFileSync(LOG_PATH, "");
69
127
  } catch {
70
128
  }
71
129
  function ts() {
@@ -84,7 +142,7 @@ var tags = {
84
142
  function appendFile(tag, args) {
85
143
  const line = `${ts()} ${tag} ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
86
144
  `;
87
- import_fs.default.appendFile(LOG_PATH, line, () => {
145
+ import_fs2.default.appendFile(LOG_PATH, line, () => {
88
146
  });
89
147
  }
90
148
  function log(tag, ...args) {
@@ -99,6 +157,81 @@ var logger = { log, err };
99
157
 
100
158
  // src/core/providers/claude-code.ts
101
159
  var SHELL = process.env.SHELL || "/bin/zsh";
160
+ function parseCommandVOutput(raw) {
161
+ const trimmed = raw.trim();
162
+ if (!trimmed) return "claude";
163
+ const aliasMatch = trimmed.match(/=\s*['"]?([^'"]+?)['"]?\s*$/);
164
+ if (aliasMatch) return aliasMatch[1];
165
+ const pathMatch = trimmed.match(/^(\/\S+)/m);
166
+ if (pathMatch) return pathMatch[1];
167
+ return trimmed;
168
+ }
169
+ function validateClaudePath(claudePath) {
170
+ try {
171
+ const claudeDir = import_path3.default.dirname(claudePath);
172
+ const env = { ...process.env, PATH: `${claudeDir}:${process.env.PATH ?? ""}` };
173
+ const out = (0, import_child_process.execSync)(`"${claudePath}" --version`, { encoding: "utf8", stdio: "pipe", timeout: 1e4, env }).trim();
174
+ return { ok: true, version: out.split("\n")[0].slice(0, 30) };
175
+ } catch {
176
+ return { ok: false };
177
+ }
178
+ }
179
+ function cacheClaudePath(claudePath, cacheDir) {
180
+ const dir = cacheDir ?? import_path3.default.join(process.cwd(), ".sna");
181
+ try {
182
+ if (!import_fs3.default.existsSync(dir)) import_fs3.default.mkdirSync(dir, { recursive: true });
183
+ import_fs3.default.writeFileSync(import_path3.default.join(dir, "claude-path"), claudePath);
184
+ } catch {
185
+ }
186
+ }
187
+ function resolveClaudeCli(opts) {
188
+ const cacheDir = opts?.cacheDir;
189
+ if (process.env.SNA_CLAUDE_COMMAND) {
190
+ const v = validateClaudePath(process.env.SNA_CLAUDE_COMMAND);
191
+ return { path: process.env.SNA_CLAUDE_COMMAND, version: v.version, source: "env" };
192
+ }
193
+ const cacheFile = cacheDir ? import_path3.default.join(cacheDir, "claude-path") : import_path3.default.join(process.cwd(), ".sna/claude-path");
194
+ try {
195
+ const cached = import_fs3.default.readFileSync(cacheFile, "utf8").trim();
196
+ if (cached) {
197
+ const v = validateClaudePath(cached);
198
+ if (v.ok) return { path: cached, version: v.version, source: "cache" };
199
+ }
200
+ } catch {
201
+ }
202
+ const staticPaths = [
203
+ "/opt/homebrew/bin/claude",
204
+ "/usr/local/bin/claude",
205
+ `${process.env.HOME}/.local/bin/claude`,
206
+ `${process.env.HOME}/.claude/bin/claude`,
207
+ `${process.env.HOME}/.volta/bin/claude`
208
+ ];
209
+ for (const p of staticPaths) {
210
+ const v = validateClaudePath(p);
211
+ if (v.ok) {
212
+ cacheClaudePath(p, cacheDir);
213
+ return { path: p, version: v.version, source: "static" };
214
+ }
215
+ }
216
+ try {
217
+ const raw = (0, import_child_process.execSync)(`${SHELL} -i -l -c "command -v claude" 2>/dev/null`, { encoding: "utf8", timeout: 5e3 }).trim();
218
+ const resolved = parseCommandVOutput(raw);
219
+ if (resolved && resolved !== "claude") {
220
+ const v = validateClaudePath(resolved);
221
+ if (v.ok) {
222
+ cacheClaudePath(resolved, cacheDir);
223
+ return { path: resolved, version: v.version, source: "shell" };
224
+ }
225
+ }
226
+ } catch {
227
+ }
228
+ return { path: "claude", source: "fallback" };
229
+ }
230
+ function resolveClaudePath(cwd) {
231
+ const result = resolveClaudeCli({ cacheDir: import_path3.default.join(cwd, ".sna") });
232
+ logger.log("agent", `claude path: ${result.source}=${result.path}${result.version ? ` (${result.version})` : ""}`);
233
+ return result.path;
234
+ }
102
235
  var _ClaudeCodeProcess = class _ClaudeCodeProcess {
103
236
  constructor(proc, options) {
104
237
  this.emitter = new import_events.EventEmitter();
@@ -440,16 +573,204 @@ var _ClaudeCodeProcess = class _ClaudeCodeProcess {
440
573
  };
441
574
  _ClaudeCodeProcess.DRAIN_INTERVAL_MS = 15;
442
575
  var ClaudeCodeProcess = _ClaudeCodeProcess;
576
+ var ClaudeCodeProvider = class {
577
+ constructor() {
578
+ this.name = "claude-code";
579
+ }
580
+ async isAvailable() {
581
+ try {
582
+ const p = resolveClaudePath(process.cwd());
583
+ (0, import_child_process.execSync)(`test -x "${p}"`, { stdio: "pipe" });
584
+ return true;
585
+ } catch {
586
+ return false;
587
+ }
588
+ }
589
+ spawn(options) {
590
+ const claudeCommand = resolveClaudePath(options.cwd);
591
+ const claudeParts = claudeCommand.split(/\s+/);
592
+ const claudePath = claudeParts[0];
593
+ const claudePrefix = claudeParts.slice(1);
594
+ let pkgRoot = import_path3.default.dirname((0, import_url.fileURLToPath)(importMetaUrl));
595
+ while (!import_fs3.default.existsSync(import_path3.default.join(pkgRoot, "package.json"))) {
596
+ const parent = import_path3.default.dirname(pkgRoot);
597
+ if (parent === pkgRoot) break;
598
+ pkgRoot = parent;
599
+ }
600
+ const hookScript = import_path3.default.join(pkgRoot, "dist", "scripts", "hook.js");
601
+ const sessionId = options.env?.SNA_SESSION_ID ?? "default";
602
+ const sdkSettings = {};
603
+ if (options.permissionMode !== "bypassPermissions") {
604
+ sdkSettings.hooks = {
605
+ PreToolUse: [{
606
+ matcher: ".*",
607
+ hooks: [{ type: "command", command: `node "${hookScript}" --session=${sessionId}` }]
608
+ }]
609
+ };
610
+ }
611
+ let extraArgsClean = options.extraArgs ? [...options.extraArgs] : [];
612
+ const settingsIdx = extraArgsClean.indexOf("--settings");
613
+ if (settingsIdx !== -1 && settingsIdx + 1 < extraArgsClean.length) {
614
+ try {
615
+ const appSettings = JSON.parse(extraArgsClean[settingsIdx + 1]);
616
+ if (appSettings.hooks) {
617
+ for (const [event, hooks] of Object.entries(appSettings.hooks)) {
618
+ if (sdkSettings.hooks && sdkSettings.hooks[event]) {
619
+ sdkSettings.hooks[event] = [
620
+ ...sdkSettings.hooks[event],
621
+ ...hooks
622
+ ];
623
+ } else {
624
+ sdkSettings.hooks[event] = hooks;
625
+ }
626
+ }
627
+ delete appSettings.hooks;
628
+ }
629
+ Object.assign(sdkSettings, appSettings);
630
+ } catch {
631
+ }
632
+ extraArgsClean.splice(settingsIdx, 2);
633
+ }
634
+ const args = [
635
+ "--output-format",
636
+ "stream-json",
637
+ "--input-format",
638
+ "stream-json",
639
+ "--verbose",
640
+ "--include-partial-messages",
641
+ "--settings",
642
+ JSON.stringify(sdkSettings)
643
+ ];
644
+ if (options.model) {
645
+ args.push("--model", options.model);
646
+ }
647
+ if (options.permissionMode) {
648
+ args.push("--permission-mode", options.permissionMode);
649
+ }
650
+ if (options.history?.length && options.prompt) {
651
+ const result = writeHistoryJsonl(options.history, { cwd: options.cwd });
652
+ if (result) {
653
+ args.push(...result.extraArgs);
654
+ options._historyViaResume = true;
655
+ logger.log("agent", `history via JSONL resume \u2192 ${result.filePath}`);
656
+ }
657
+ }
658
+ if (extraArgsClean.length > 0) {
659
+ args.push(...extraArgsClean);
660
+ }
661
+ const cleanEnv = { ...process.env, ...options.env };
662
+ if (options.configDir) {
663
+ cleanEnv.CLAUDE_CONFIG_DIR = options.configDir;
664
+ }
665
+ delete cleanEnv.CLAUDECODE;
666
+ delete cleanEnv.CLAUDE_CODE_ENTRYPOINT;
667
+ delete cleanEnv.CLAUDE_CODE_SESSION_ACCESS_TOKEN;
668
+ delete cleanEnv.CLAUDE_CODE_OAUTH_TOKEN;
669
+ const claudeDir = import_path3.default.dirname(claudePath);
670
+ if (claudeDir && claudeDir !== ".") {
671
+ cleanEnv.PATH = `${claudeDir}:${cleanEnv.PATH ?? ""}`;
672
+ }
673
+ const proc = (0, import_child_process.spawn)(claudePath, [...claudePrefix, ...args], {
674
+ cwd: options.cwd,
675
+ env: cleanEnv,
676
+ stdio: ["pipe", "pipe", "pipe"]
677
+ });
678
+ logger.log("agent", `spawned claude-code (pid=${proc.pid}) \u2192 ${claudeCommand} ${args.join(" ")}`);
679
+ return new ClaudeCodeProcess(proc, options);
680
+ }
681
+ };
682
+
683
+ // src/electron/index.ts
684
+ var import_path6 = __toESM(require("path"), 1);
685
+ var import_hono4 = require("hono");
686
+ var import_cors = require("hono/cors");
687
+ var import_node_server = require("@hono/node-server");
688
+
689
+ // src/server/index.ts
690
+ var import_hono3 = require("hono");
691
+
692
+ // src/server/routes/events.ts
693
+ var import_streaming = require("hono/streaming");
694
+
695
+ // src/db/schema.ts
696
+ var import_node_module = require("module");
697
+ var import_path4 = __toESM(require("path"), 1);
698
+ var DB_PATH = process.env.SNA_DB_PATH ?? import_path4.default.join(process.cwd(), "data/sna.db");
699
+ var NATIVE_DIR = import_path4.default.join(process.cwd(), ".sna/native");
700
+
701
+ // src/config.ts
702
+ var defaults = {
703
+ port: 3099,
704
+ model: "claude-sonnet-4-6",
705
+ defaultProvider: "claude-code",
706
+ defaultPermissionMode: "default",
707
+ maxSessions: 5,
708
+ maxEventBuffer: 500,
709
+ permissionTimeoutMs: 0,
710
+ // app controls — no SDK-side timeout
711
+ runOnceTimeoutMs: 12e4,
712
+ pollIntervalMs: 500,
713
+ keepaliveIntervalMs: 15e3,
714
+ skillPollMs: 2e3,
715
+ dbPath: "data/sna.db"
716
+ };
717
+ function fromEnv() {
718
+ const env = {};
719
+ if (process.env.SNA_PORT) env.port = parseInt(process.env.SNA_PORT, 10);
720
+ if (process.env.SNA_MODEL) env.model = process.env.SNA_MODEL;
721
+ if (process.env.SNA_PERMISSION_MODE) env.defaultPermissionMode = process.env.SNA_PERMISSION_MODE;
722
+ if (process.env.SNA_MAX_SESSIONS) env.maxSessions = parseInt(process.env.SNA_MAX_SESSIONS, 10);
723
+ if (process.env.SNA_DB_PATH) env.dbPath = process.env.SNA_DB_PATH;
724
+ if (process.env.SNA_PERMISSION_TIMEOUT_MS) env.permissionTimeoutMs = parseInt(process.env.SNA_PERMISSION_TIMEOUT_MS, 10);
725
+ return env;
726
+ }
727
+ var current = { ...defaults, ...fromEnv() };
728
+
729
+ // src/server/routes/run.ts
730
+ var import_streaming2 = require("hono/streaming");
731
+ var ROOT = process.cwd();
732
+
733
+ // src/server/routes/agent.ts
734
+ var import_hono = require("hono");
735
+ var import_streaming3 = require("hono/streaming");
736
+
737
+ // src/core/providers/codex.ts
738
+ var CodexProvider = class {
739
+ constructor() {
740
+ this.name = "codex";
741
+ }
742
+ async isAvailable() {
743
+ return false;
744
+ }
745
+ spawn(_options) {
746
+ throw new Error("Codex provider not yet implemented");
747
+ }
748
+ };
749
+
750
+ // src/core/providers/index.ts
751
+ var providers = {
752
+ "claude-code": new ClaudeCodeProvider(),
753
+ "codex": new CodexProvider()
754
+ };
755
+
756
+ // src/server/image-store.ts
757
+ var import_path5 = __toESM(require("path"), 1);
758
+ var IMAGE_DIR = import_path5.default.join(process.cwd(), "data/images");
759
+
760
+ // src/server/routes/chat.ts
761
+ var import_hono2 = require("hono");
762
+
763
+ // src/server/ws.ts
764
+ var import_ws = require("ws");
443
765
 
444
766
  // src/electron/index.ts
445
- var import_path2 = __toESM(require("path"), 1);
446
767
  function resolveStandaloneScript() {
447
- const selfPath = (0, import_url.fileURLToPath)(importMetaUrl);
448
- let script = import_path2.default.resolve(import_path2.default.dirname(selfPath), "../server/standalone.js");
768
+ const selfPath = (0, import_url2.fileURLToPath)(importMetaUrl);
769
+ let script = import_path6.default.resolve(import_path6.default.dirname(selfPath), "../server/standalone.js");
449
770
  if (script.includes(".asar") && !script.includes(".asar.unpacked")) {
450
771
  script = script.replace(/(\.asar)([/\\])/, ".asar.unpacked$2");
451
772
  }
452
- if (!import_fs2.default.existsSync(script)) {
773
+ if (!import_fs4.default.existsSync(script)) {
453
774
  throw new Error(
454
775
  `SNA standalone script not found: ${script}
455
776
  Ensure "@sna-sdk/core" is listed in asarUnpack in your electron-builder config.`
@@ -460,14 +781,14 @@ Ensure "@sna-sdk/core" is listed in asarUnpack in your electron-builder config.`
460
781
  function buildNodePath() {
461
782
  const resourcesPath = process.resourcesPath;
462
783
  if (!resourcesPath) return void 0;
463
- const unpacked = import_path2.default.join(resourcesPath, "app.asar.unpacked", "node_modules");
464
- if (!import_fs2.default.existsSync(unpacked)) return void 0;
784
+ const unpacked = import_path6.default.join(resourcesPath, "app.asar.unpacked", "node_modules");
785
+ if (!import_fs4.default.existsSync(unpacked)) return void 0;
465
786
  const existing = process.env.NODE_PATH;
466
- return existing ? `${unpacked}${import_path2.default.delimiter}${existing}` : unpacked;
787
+ return existing ? `${unpacked}${import_path6.default.delimiter}${existing}` : unpacked;
467
788
  }
468
789
  async function startSnaServer(options) {
469
790
  const port = options.port ?? 3099;
470
- const cwd = options.cwd ?? import_path2.default.dirname(options.dbPath);
791
+ const cwd = options.cwd ?? import_path6.default.dirname(options.dbPath);
471
792
  const readyTimeout = options.readyTimeout ?? 15e3;
472
793
  const { onLog } = options;
473
794
  const standaloneScript = resolveStandaloneScript();
@@ -475,7 +796,7 @@ async function startSnaServer(options) {
475
796
  let consumerModules;
476
797
  try {
477
798
  const bsPkg = require.resolve("better-sqlite3/package.json", { paths: [process.cwd()] });
478
- consumerModules = import_path2.default.resolve(bsPkg, "../..");
799
+ consumerModules = import_path6.default.resolve(bsPkg, "../..");
479
800
  } catch {
480
801
  }
481
802
  const env = {
@@ -492,7 +813,7 @@ async function startSnaServer(options) {
492
813
  // Consumer overrides last so they can always win
493
814
  ...options.env ?? {}
494
815
  };
495
- const proc = (0, import_child_process.fork)(standaloneScript, [], {
816
+ const proc = (0, import_child_process2.fork)(standaloneScript, [], {
496
817
  cwd,
497
818
  env,
498
819
  stdio: "pipe"
@@ -1,4 +1,6 @@
1
1
  export { SnaServerHandle, SnaServerOptions, startSnaServer } from '../electron/index.js';
2
2
  import 'child_process';
3
+ import 'http';
3
4
  import '../core/providers/claude-code.js';
4
5
  import '../core/providers/types.js';
6
+ import '../server/session-manager.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sna-sdk/core",
3
- "version": "0.9.10",
3
+ "version": "0.9.11",
4
4
  "description": "Skills-Native Application runtime — server, providers, session management, database, and CLI",
5
5
  "type": "module",
6
6
  "bin": {