@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.
- package/dist/electron/index.cjs +2375 -26
- package/dist/electron/index.d.ts +31 -1
- package/dist/electron/index.js +80 -0
- package/dist/node/index.cjs +339 -18
- package/dist/node/index.d.ts +2 -0
- package/package.json +1 -1
package/dist/electron/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/electron/index.js
CHANGED
|
@@ -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
|
};
|
package/dist/node/index.cjs
CHANGED
|
@@ -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
|
|
43
|
-
var
|
|
44
|
-
var
|
|
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
|
|
65
|
-
var
|
|
66
|
-
var LOG_PATH =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
448
|
-
let script =
|
|
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 (!
|
|
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 =
|
|
464
|
-
if (!
|
|
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}${
|
|
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 ??
|
|
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 =
|
|
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,
|
|
816
|
+
const proc = (0, import_child_process2.fork)(standaloneScript, [], {
|
|
496
817
|
cwd,
|
|
497
818
|
env,
|
|
498
819
|
stdio: "pipe"
|
package/dist/node/index.d.ts
CHANGED