@sna-sdk/core 0.1.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -7
- package/dist/core/providers/claude-code.js +9 -3
- package/dist/core/providers/types.d.ts +2 -0
- package/dist/db/schema.d.ts +1 -0
- package/dist/db/schema.js +19 -2
- package/dist/lib/logger.d.ts +1 -0
- package/dist/lib/logger.js +2 -0
- package/dist/scripts/hook.js +1 -1
- package/dist/scripts/sna.js +50 -0
- package/dist/server/api-types.d.ts +105 -0
- package/dist/server/api-types.js +13 -0
- package/dist/server/index.d.ts +5 -2
- package/dist/server/index.js +7 -4
- package/dist/server/routes/agent.d.ts +21 -1
- package/dist/server/routes/agent.js +125 -59
- package/dist/server/routes/chat.js +8 -7
- package/dist/server/routes/emit.d.ts +11 -1
- package/dist/server/routes/emit.js +26 -0
- package/dist/server/session-manager.d.ts +58 -1
- package/dist/server/session-manager.js +207 -2
- package/dist/server/standalone.js +884 -84
- package/dist/server/ws.d.ts +55 -0
- package/dist/server/ws.js +485 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,10 +6,13 @@ Server runtime for [Skills-Native Applications](https://github.com/neuradex/sna)
|
|
|
6
6
|
|
|
7
7
|
- **Skill event pipeline** — emit, SSE streaming, and hook scripts
|
|
8
8
|
- **Dispatch** — unified event dispatcher with validation, session lifecycle, and cleanup (`sna dispatch` CLI + programmatic API)
|
|
9
|
-
- **SQLite database** — schema and `getDb()` for `skill_events`
|
|
10
|
-
- **Hono server factory** — `createSnaApp()` with events, emit, agent, and run routes
|
|
9
|
+
- **SQLite database** — schema and `getDb()` for `skill_events`, `chat_sessions`, `chat_messages`
|
|
10
|
+
- **Hono server factory** — `createSnaApp()` with events, emit, agent, chat, and run routes
|
|
11
|
+
- **WebSocket API** — `attachWebSocket()` wrapping all HTTP routes over a single WS connection
|
|
12
|
+
- **One-shot execution** — `POST /agent/run-once` for single-request LLM calls
|
|
11
13
|
- **Lifecycle CLI** — `sna api:up`, `sna api:down`, `sna dispatch`, `sna validate`
|
|
12
14
|
- **Agent providers** — Claude Code and Codex process management
|
|
15
|
+
- **Multi-session** — `SessionManager` with event pub/sub, permission management, and session metadata
|
|
13
16
|
|
|
14
17
|
## Install
|
|
15
18
|
|
|
@@ -53,10 +56,14 @@ Event types: `start` | `progress` | `milestone` | `complete` | `error`
|
|
|
53
56
|
### Mount server routes
|
|
54
57
|
|
|
55
58
|
```ts
|
|
56
|
-
import { createSnaApp } from "@sna-sdk/core/server";
|
|
59
|
+
import { createSnaApp, attachWebSocket } from "@sna-sdk/core/server";
|
|
60
|
+
import { serve } from "@hono/node-server";
|
|
57
61
|
|
|
58
62
|
const sna = createSnaApp();
|
|
59
|
-
//
|
|
63
|
+
// HTTP: GET /health, GET /events (SSE), POST /emit, /agent/*, /chat/*
|
|
64
|
+
const server = serve({ fetch: sna.fetch, port: 3099 });
|
|
65
|
+
// WS: ws://localhost:3099/ws — all routes available over WebSocket
|
|
66
|
+
attachWebSocket(server, sessionManager);
|
|
60
67
|
```
|
|
61
68
|
|
|
62
69
|
### Access the database
|
|
@@ -71,9 +78,10 @@ const db = getDb(); // SQLite instance (data/sna.db)
|
|
|
71
78
|
|
|
72
79
|
| Import path | Contents |
|
|
73
80
|
|-------------|----------|
|
|
74
|
-
| `@sna-sdk/core` | `DEFAULT_SNA_PORT`, `DEFAULT_SNA_URL`, `dispatchOpen`, `dispatchSend`, `dispatchClose`, `createDispatchHandle`, `
|
|
75
|
-
| `@sna-sdk/core/server` | `createSnaApp()`, route handlers, `SessionManager` |
|
|
76
|
-
| `@sna-sdk/core/
|
|
81
|
+
| `@sna-sdk/core` | `DEFAULT_SNA_PORT`, `DEFAULT_SNA_URL`, `dispatchOpen`, `dispatchSend`, `dispatchClose`, `createDispatchHandle`, types (`AgentEvent`, `Session`, `SessionInfo`, `ChatSession`, `ChatMessage`, `SkillEvent`, etc.) |
|
|
82
|
+
| `@sna-sdk/core/server` | `createSnaApp()`, `attachWebSocket()`, route handlers, `SessionManager` |
|
|
83
|
+
| `@sna-sdk/core/server/routes/agent` | `createAgentRoutes()`, `runOnce()` |
|
|
84
|
+
| `@sna-sdk/core/db/schema` | `getDb()`, `ChatSession`, `ChatMessage`, `SkillEvent` types |
|
|
77
85
|
| `@sna-sdk/core/providers` | Agent provider factory, `ClaudeCodeProvider` |
|
|
78
86
|
| `@sna-sdk/core/lib/sna-run` | `snaRun()` helper for spawning Claude Code |
|
|
79
87
|
|
|
@@ -99,6 +99,11 @@ class ClaudeCodeProcess {
|
|
|
99
99
|
logger.log("stdin", msg.slice(0, 200));
|
|
100
100
|
this.proc.stdin.write(msg + "\n");
|
|
101
101
|
}
|
|
102
|
+
interrupt() {
|
|
103
|
+
if (this._alive) {
|
|
104
|
+
this.proc.kill("SIGINT");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
102
107
|
kill() {
|
|
103
108
|
if (this._alive) {
|
|
104
109
|
this._alive = false;
|
|
@@ -196,7 +201,7 @@ class ClaudeCodeProcess {
|
|
|
196
201
|
timestamp: Date.now()
|
|
197
202
|
};
|
|
198
203
|
}
|
|
199
|
-
if (msg.subtype
|
|
204
|
+
if (msg.subtype?.startsWith("error") || msg.is_error) {
|
|
200
205
|
return {
|
|
201
206
|
type: "error",
|
|
202
207
|
message: msg.result ?? msg.error ?? "Unknown error",
|
|
@@ -228,13 +233,14 @@ class ClaudeCodeProvider {
|
|
|
228
233
|
}
|
|
229
234
|
spawn(options) {
|
|
230
235
|
const claudePath = resolveClaudePath(options.cwd);
|
|
231
|
-
const hookScript =
|
|
236
|
+
const hookScript = new URL("../../scripts/hook.js", import.meta.url).pathname;
|
|
237
|
+
const sessionId = options.env?.SNA_SESSION_ID ?? "default";
|
|
232
238
|
const sdkSettings = {};
|
|
233
239
|
if (options.permissionMode !== "bypassPermissions") {
|
|
234
240
|
sdkSettings.hooks = {
|
|
235
241
|
PreToolUse: [{
|
|
236
242
|
matcher: ".*",
|
|
237
|
-
hooks: [{ type: "command", command: `node "${hookScript}"` }]
|
|
243
|
+
hooks: [{ type: "command", command: `node "${hookScript}" --session=${sessionId}` }]
|
|
238
244
|
}]
|
|
239
245
|
};
|
|
240
246
|
}
|
|
@@ -16,6 +16,8 @@ interface AgentEvent {
|
|
|
16
16
|
interface AgentProcess {
|
|
17
17
|
/** Send a user message to the agent's stdin. */
|
|
18
18
|
send(input: string): void;
|
|
19
|
+
/** Interrupt the current turn (SIGINT). Process stays alive. */
|
|
20
|
+
interrupt(): void;
|
|
19
21
|
/** Kill the agent process. */
|
|
20
22
|
kill(): void;
|
|
21
23
|
/** Whether the process is still running. */
|
package/dist/db/schema.d.ts
CHANGED
package/dist/db/schema.js
CHANGED
|
@@ -2,11 +2,20 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
const DB_PATH = path.join(process.cwd(), "data/sna.db");
|
|
5
|
+
const NATIVE_DIR = path.join(process.cwd(), ".sna/native");
|
|
5
6
|
let _db = null;
|
|
7
|
+
function loadBetterSqlite3() {
|
|
8
|
+
const nativeEntry = path.join(NATIVE_DIR, "node_modules", "better-sqlite3");
|
|
9
|
+
if (fs.existsSync(nativeEntry)) {
|
|
10
|
+
const req2 = createRequire(path.join(NATIVE_DIR, "noop.js"));
|
|
11
|
+
return req2("better-sqlite3");
|
|
12
|
+
}
|
|
13
|
+
const req = createRequire(import.meta.url);
|
|
14
|
+
return req("better-sqlite3");
|
|
15
|
+
}
|
|
6
16
|
function getDb() {
|
|
7
17
|
if (!_db) {
|
|
8
|
-
const
|
|
9
|
-
const BetterSqlite3 = req("better-sqlite3");
|
|
18
|
+
const BetterSqlite3 = loadBetterSqlite3();
|
|
10
19
|
const dir = path.dirname(DB_PATH);
|
|
11
20
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
12
21
|
_db = new BetterSqlite3(DB_PATH);
|
|
@@ -28,6 +37,12 @@ function migrateChatSessionsMeta(db) {
|
|
|
28
37
|
if (cols.length > 0 && !cols.some((c) => c.name === "meta")) {
|
|
29
38
|
db.exec("ALTER TABLE chat_sessions ADD COLUMN meta TEXT");
|
|
30
39
|
}
|
|
40
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "cwd")) {
|
|
41
|
+
db.exec("ALTER TABLE chat_sessions ADD COLUMN cwd TEXT");
|
|
42
|
+
}
|
|
43
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "last_start_config")) {
|
|
44
|
+
db.exec("ALTER TABLE chat_sessions ADD COLUMN last_start_config TEXT");
|
|
45
|
+
}
|
|
31
46
|
}
|
|
32
47
|
function initSchema(db) {
|
|
33
48
|
migrateSkillEvents(db);
|
|
@@ -38,6 +53,8 @@ function initSchema(db) {
|
|
|
38
53
|
label TEXT NOT NULL DEFAULT '',
|
|
39
54
|
type TEXT NOT NULL DEFAULT 'main',
|
|
40
55
|
meta TEXT,
|
|
56
|
+
cwd TEXT,
|
|
57
|
+
last_start_config TEXT,
|
|
41
58
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
42
59
|
);
|
|
43
60
|
|
package/dist/lib/logger.d.ts
CHANGED
package/dist/lib/logger.js
CHANGED
|
@@ -19,6 +19,7 @@ const tags = {
|
|
|
19
19
|
stdin: chalk.bold.green(" IN "),
|
|
20
20
|
stdout: chalk.bold.yellow(" OUT "),
|
|
21
21
|
route: chalk.bold.blue(" API "),
|
|
22
|
+
ws: chalk.bold.green(" WS "),
|
|
22
23
|
err: chalk.bold.red(" ERR ")
|
|
23
24
|
};
|
|
24
25
|
const tagPlain = {
|
|
@@ -28,6 +29,7 @@ const tagPlain = {
|
|
|
28
29
|
stdin: " IN ",
|
|
29
30
|
stdout: " OUT ",
|
|
30
31
|
route: " API ",
|
|
32
|
+
ws: " WS ",
|
|
31
33
|
err: " ERR "
|
|
32
34
|
};
|
|
33
35
|
function appendFile(tag, args) {
|
package/dist/scripts/hook.js
CHANGED
|
@@ -24,7 +24,7 @@ process.stdin.on("end", async () => {
|
|
|
24
24
|
allow();
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
const sessionId = process.env.SNA_SESSION_ID ?? "default";
|
|
27
|
+
const sessionId = process.argv.find((a) => a.startsWith("--session="))?.slice(10) ?? process.env.SNA_SESSION_ID ?? "default";
|
|
28
28
|
const apiUrl = `http://localhost:${port}`;
|
|
29
29
|
const res = await fetch(`${apiUrl}/agent/permission-request?session=${encodeURIComponent(sessionId)}`, {
|
|
30
30
|
method: "POST",
|
package/dist/scripts/sna.js
CHANGED
|
@@ -16,6 +16,7 @@ const SNA_API_LOG_FILE = path.join(STATE_DIR, "sna-api.log");
|
|
|
16
16
|
const PORT = process.env.PORT ?? "3000";
|
|
17
17
|
const CLAUDE_PATH_FILE = path.join(STATE_DIR, "claude-path");
|
|
18
18
|
const SNA_CORE_DIR = path.join(ROOT, "node_modules/@sna-sdk/core");
|
|
19
|
+
const NATIVE_DIR = path.join(STATE_DIR, "native");
|
|
19
20
|
function ensureStateDir() {
|
|
20
21
|
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
21
22
|
}
|
|
@@ -76,8 +77,57 @@ async function checkSnaApiHealth(port) {
|
|
|
76
77
|
return false;
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
function ensureNativeDeps() {
|
|
81
|
+
const marker = path.join(NATIVE_DIR, "node_modules", "better-sqlite3", "build", "Release", "better_sqlite3.node");
|
|
82
|
+
if (fs.existsSync(marker)) {
|
|
83
|
+
try {
|
|
84
|
+
const { createRequire } = require("module");
|
|
85
|
+
const req = createRequire(path.join(NATIVE_DIR, "noop.js"));
|
|
86
|
+
const BS3 = req("better-sqlite3");
|
|
87
|
+
new BS3(":memory:").close();
|
|
88
|
+
return;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (!err.message?.includes("NODE_MODULE_VERSION")) return;
|
|
91
|
+
step("Native binary version mismatch \u2014 reinstalling...");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
let version;
|
|
95
|
+
try {
|
|
96
|
+
const pkgPath = require.resolve("better-sqlite3/package.json", { paths: [SNA_CORE_DIR, ROOT] });
|
|
97
|
+
version = JSON.parse(fs.readFileSync(pkgPath, "utf8")).version;
|
|
98
|
+
} catch {
|
|
99
|
+
version = "^12.0.0";
|
|
100
|
+
}
|
|
101
|
+
step(`Installing isolated better-sqlite3@${version} in .sna/native/`);
|
|
102
|
+
fs.mkdirSync(NATIVE_DIR, { recursive: true });
|
|
103
|
+
fs.writeFileSync(path.join(NATIVE_DIR, "package.json"), JSON.stringify({
|
|
104
|
+
name: "sna-native-deps",
|
|
105
|
+
private: true,
|
|
106
|
+
dependencies: { "better-sqlite3": version }
|
|
107
|
+
}));
|
|
108
|
+
try {
|
|
109
|
+
execSync("npm install --no-package-lock --ignore-scripts", { cwd: NATIVE_DIR, stdio: "pipe" });
|
|
110
|
+
execSync("npx --yes prebuild-install -r napi", {
|
|
111
|
+
cwd: path.join(NATIVE_DIR, "node_modules", "better-sqlite3"),
|
|
112
|
+
stdio: "pipe"
|
|
113
|
+
});
|
|
114
|
+
step("Native deps ready");
|
|
115
|
+
} catch (err) {
|
|
116
|
+
try {
|
|
117
|
+
execSync("npm rebuild better-sqlite3", { cwd: NATIVE_DIR, stdio: "pipe" });
|
|
118
|
+
step("Native deps ready (compiled from source)");
|
|
119
|
+
} catch {
|
|
120
|
+
console.error(`
|
|
121
|
+
\u2717 Failed to install isolated better-sqlite3: ${err.message}`);
|
|
122
|
+
console.error(` Try manually: cd .sna/native && npm install
|
|
123
|
+
`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
79
128
|
async function cmdApiUp() {
|
|
80
129
|
const standaloneEntry = path.join(SNA_CORE_DIR, "dist/server/standalone.js");
|
|
130
|
+
ensureNativeDeps();
|
|
81
131
|
const existingPort = process.env.SNA_PORT ?? readSnaApiPort();
|
|
82
132
|
if (existingPort && isPortInUse(existingPort)) {
|
|
83
133
|
const healthy = await checkSnaApiHealth(existingPort);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as hono from 'hono';
|
|
2
|
+
import { Context } from 'hono';
|
|
3
|
+
import { WebSocket } from 'ws';
|
|
4
|
+
import { SessionInfo } from './session-manager.js';
|
|
5
|
+
import '../core/providers/types.js';
|
|
6
|
+
|
|
7
|
+
interface ApiResponses {
|
|
8
|
+
"sessions.create": {
|
|
9
|
+
status: "created";
|
|
10
|
+
sessionId: string;
|
|
11
|
+
label: string;
|
|
12
|
+
meta: Record<string, unknown> | null;
|
|
13
|
+
};
|
|
14
|
+
"sessions.list": {
|
|
15
|
+
sessions: SessionInfo[];
|
|
16
|
+
};
|
|
17
|
+
"sessions.remove": {
|
|
18
|
+
status: "removed";
|
|
19
|
+
};
|
|
20
|
+
"agent.start": {
|
|
21
|
+
status: "started" | "already_running";
|
|
22
|
+
provider: string;
|
|
23
|
+
sessionId: string;
|
|
24
|
+
};
|
|
25
|
+
"agent.send": {
|
|
26
|
+
status: "sent";
|
|
27
|
+
};
|
|
28
|
+
"agent.restart": {
|
|
29
|
+
status: "restarted";
|
|
30
|
+
provider: string;
|
|
31
|
+
sessionId: string;
|
|
32
|
+
};
|
|
33
|
+
"agent.interrupt": {
|
|
34
|
+
status: "interrupted" | "no_session";
|
|
35
|
+
};
|
|
36
|
+
"agent.kill": {
|
|
37
|
+
status: "killed" | "no_session";
|
|
38
|
+
};
|
|
39
|
+
"agent.status": {
|
|
40
|
+
alive: boolean;
|
|
41
|
+
sessionId: string | null;
|
|
42
|
+
eventCount: number;
|
|
43
|
+
};
|
|
44
|
+
"agent.run-once": {
|
|
45
|
+
result: string;
|
|
46
|
+
usage: Record<string, unknown> | null;
|
|
47
|
+
};
|
|
48
|
+
"emit": {
|
|
49
|
+
id: number;
|
|
50
|
+
};
|
|
51
|
+
"permission.respond": {
|
|
52
|
+
status: "approved" | "denied";
|
|
53
|
+
};
|
|
54
|
+
"permission.pending": {
|
|
55
|
+
pending: Array<{
|
|
56
|
+
sessionId: string;
|
|
57
|
+
request: Record<string, unknown>;
|
|
58
|
+
createdAt: number;
|
|
59
|
+
}>;
|
|
60
|
+
};
|
|
61
|
+
"chat.sessions.list": {
|
|
62
|
+
sessions: Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
label: string;
|
|
65
|
+
type: string;
|
|
66
|
+
meta: Record<string, unknown> | null;
|
|
67
|
+
cwd: string | null;
|
|
68
|
+
created_at: string;
|
|
69
|
+
}>;
|
|
70
|
+
};
|
|
71
|
+
"chat.sessions.create": {
|
|
72
|
+
status: "created";
|
|
73
|
+
id: string;
|
|
74
|
+
meta: Record<string, unknown> | null;
|
|
75
|
+
};
|
|
76
|
+
"chat.sessions.remove": {
|
|
77
|
+
status: "deleted";
|
|
78
|
+
};
|
|
79
|
+
"chat.messages.list": {
|
|
80
|
+
messages: unknown[];
|
|
81
|
+
};
|
|
82
|
+
"chat.messages.create": {
|
|
83
|
+
status: "created";
|
|
84
|
+
id: number;
|
|
85
|
+
};
|
|
86
|
+
"chat.messages.clear": {
|
|
87
|
+
status: "cleared";
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
type ApiOp = keyof ApiResponses;
|
|
91
|
+
/**
|
|
92
|
+
* Type-safe JSON response for HTTP routes.
|
|
93
|
+
* Ensures the response body matches the defined shape for the operation.
|
|
94
|
+
*/
|
|
95
|
+
declare function httpJson<K extends ApiOp>(c: Context, _op: K, data: ApiResponses[K], status?: number): Response & hono.TypedResponse<any, any, "json">;
|
|
96
|
+
/**
|
|
97
|
+
* Type-safe reply for WS handlers.
|
|
98
|
+
* Ensures the response data matches the defined shape for the operation.
|
|
99
|
+
*/
|
|
100
|
+
declare function wsReply<K extends ApiOp>(ws: WebSocket, msg: {
|
|
101
|
+
type: string;
|
|
102
|
+
rid?: string;
|
|
103
|
+
}, data: ApiResponses[K]): void;
|
|
104
|
+
|
|
105
|
+
export { type ApiOp, type ApiResponses, httpJson, wsReply };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function httpJson(c, _op, data, status) {
|
|
2
|
+
return c.json(data, status);
|
|
3
|
+
}
|
|
4
|
+
function wsReply(ws, msg, data) {
|
|
5
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
6
|
+
const out = { ...data, type: msg.type };
|
|
7
|
+
if (msg.rid != null) out.rid = msg.rid;
|
|
8
|
+
ws.send(JSON.stringify(out));
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
httpJson,
|
|
12
|
+
wsReply
|
|
13
|
+
};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
3
|
import { SessionManager } from './session-manager.js';
|
|
4
|
-
export { Session, SessionInfo, SessionManagerOptions } from './session-manager.js';
|
|
4
|
+
export { Session, SessionInfo, SessionLifecycleEvent, SessionLifecycleState, SessionManagerOptions, StartConfig } from './session-manager.js';
|
|
5
5
|
export { eventsRoute } from './routes/events.js';
|
|
6
|
-
export { emitRoute } from './routes/emit.js';
|
|
6
|
+
export { createEmitRoute, emitRoute } from './routes/emit.js';
|
|
7
7
|
export { createRunRoute } from './routes/run.js';
|
|
8
8
|
export { createAgentRoutes } from './routes/agent.js';
|
|
9
9
|
export { createChatRoutes } from './routes/chat.js';
|
|
10
|
+
export { attachWebSocket } from './ws.js';
|
|
10
11
|
import '../core/providers/types.js';
|
|
11
12
|
import 'hono/utils/http-status';
|
|
13
|
+
import 'ws';
|
|
14
|
+
import 'http';
|
|
12
15
|
|
|
13
16
|
interface SnaAppOptions {
|
|
14
17
|
/** Commands available via GET /run?skill=<name> */
|
package/dist/server/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import _fs from "fs";
|
|
|
2
2
|
import _path from "path";
|
|
3
3
|
import { Hono } from "hono";
|
|
4
4
|
import { eventsRoute } from "./routes/events.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createEmitRoute } from "./routes/emit.js";
|
|
6
6
|
import { createRunRoute } from "./routes/run.js";
|
|
7
7
|
import { createAgentRoutes } from "./routes/agent.js";
|
|
8
8
|
import { createChatRoutes } from "./routes/chat.js";
|
|
@@ -12,7 +12,7 @@ function createSnaApp(options = {}) {
|
|
|
12
12
|
const app = new Hono();
|
|
13
13
|
app.get("/health", (c) => c.json({ ok: true, name: "sna", version: "1" }));
|
|
14
14
|
app.get("/events", eventsRoute);
|
|
15
|
-
app.post("/emit",
|
|
15
|
+
app.post("/emit", createEmitRoute(sessionManager));
|
|
16
16
|
app.route("/agent", createAgentRoutes(sessionManager));
|
|
17
17
|
app.route("/chat", createChatRoutes());
|
|
18
18
|
if (options.runCommands) {
|
|
@@ -21,11 +21,12 @@ function createSnaApp(options = {}) {
|
|
|
21
21
|
return app;
|
|
22
22
|
}
|
|
23
23
|
import { eventsRoute as eventsRoute2 } from "./routes/events.js";
|
|
24
|
-
import { emitRoute as
|
|
24
|
+
import { emitRoute, createEmitRoute as createEmitRoute2 } from "./routes/emit.js";
|
|
25
25
|
import { createRunRoute as createRunRoute2 } from "./routes/run.js";
|
|
26
26
|
import { createAgentRoutes as createAgentRoutes2 } from "./routes/agent.js";
|
|
27
27
|
import { createChatRoutes as createChatRoutes2 } from "./routes/chat.js";
|
|
28
28
|
import { SessionManager as SessionManager2 } from "./session-manager.js";
|
|
29
|
+
import { attachWebSocket } from "./ws.js";
|
|
29
30
|
function snaPortRoute(c) {
|
|
30
31
|
const portFile = _path.join(process.cwd(), ".sna/sna-api.port");
|
|
31
32
|
try {
|
|
@@ -37,11 +38,13 @@ function snaPortRoute(c) {
|
|
|
37
38
|
}
|
|
38
39
|
export {
|
|
39
40
|
SessionManager2 as SessionManager,
|
|
41
|
+
attachWebSocket,
|
|
40
42
|
createAgentRoutes2 as createAgentRoutes,
|
|
41
43
|
createChatRoutes2 as createChatRoutes,
|
|
44
|
+
createEmitRoute2 as createEmitRoute,
|
|
42
45
|
createRunRoute2 as createRunRoute,
|
|
43
46
|
createSnaApp,
|
|
44
|
-
|
|
47
|
+
emitRoute,
|
|
45
48
|
eventsRoute2 as eventsRoute,
|
|
46
49
|
snaPortRoute
|
|
47
50
|
};
|
|
@@ -3,6 +3,26 @@ import { Hono } from 'hono';
|
|
|
3
3
|
import { SessionManager } from '../session-manager.js';
|
|
4
4
|
import '../../core/providers/types.js';
|
|
5
5
|
|
|
6
|
+
interface RunOnceOptions {
|
|
7
|
+
message: string;
|
|
8
|
+
model?: string;
|
|
9
|
+
systemPrompt?: string;
|
|
10
|
+
appendSystemPrompt?: string;
|
|
11
|
+
permissionMode?: string;
|
|
12
|
+
cwd?: string;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
provider?: string;
|
|
15
|
+
extraArgs?: string[];
|
|
16
|
+
}
|
|
17
|
+
interface RunOnceResult {
|
|
18
|
+
result: string;
|
|
19
|
+
usage: Record<string, unknown> | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One-shot agent execution: create temp session → spawn → wait for result → cleanup.
|
|
23
|
+
* Used by both HTTP POST /run-once and WS agent.run-once.
|
|
24
|
+
*/
|
|
25
|
+
declare function runOnce(sessionManager: SessionManager, opts: RunOnceOptions): Promise<RunOnceResult>;
|
|
6
26
|
declare function createAgentRoutes(sessionManager: SessionManager): Hono<hono_types.BlankEnv, hono_types.BlankSchema, "/">;
|
|
7
27
|
|
|
8
|
-
export { createAgentRoutes };
|
|
28
|
+
export { type RunOnceOptions, type RunOnceResult, createAgentRoutes, runOnce };
|