@kb-labs/gateway-runtime-server 0.2.0

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/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,135 @@
1
+ import * as path from 'path';
2
+ import { GatewayClient } from '@kb-labs/host-agent-core';
3
+ import { runInProcess } from '@kb-labs/plugin-runtime';
4
+ import { noopUI } from '@kb-labs/plugin-contracts';
5
+
6
+ // src/runtime-server.ts
7
+ var RuntimeServer = class {
8
+ constructor(opts) {
9
+ this.opts = opts;
10
+ const clientOpts = {
11
+ gatewayUrl: opts.gatewayUrl,
12
+ agentVersion: opts.agentVersion ?? "0.1.0",
13
+ getAccessToken: opts.getAccessToken,
14
+ onTokenExpired: opts.onTokenExpired,
15
+ capabilities: ["execution"],
16
+ onConnected: (hId, sessionId) => {
17
+ this.hostId = hId;
18
+ console.log(`[runtime-server] Connected to Gateway: hostId=${hId} session=${sessionId}`);
19
+ },
20
+ onDisconnected: () => {
21
+ console.log("[runtime-server] Disconnected from Gateway, reconnecting...");
22
+ }
23
+ };
24
+ this.client = new GatewayClient(clientOpts);
25
+ this.client.registerHandler("execution", (call) => this.handleExecution(call));
26
+ }
27
+ client;
28
+ hostId = null;
29
+ async start() {
30
+ await this.client.connect();
31
+ }
32
+ stop() {
33
+ this.client.stop();
34
+ }
35
+ /** The hostId assigned by Gateway after handshake (null before connected) */
36
+ getHostId() {
37
+ return this.hostId;
38
+ }
39
+ async handleExecution(call) {
40
+ if (call.method !== "execute") {
41
+ throw new Error(`Unknown execution method: ${call.method}`);
42
+ }
43
+ const request = call.args[0];
44
+ if (!request || !request.handlerRef || !request.pluginRoot) {
45
+ throw new Error("Missing required fields: handlerRef, pluginRoot");
46
+ }
47
+ return this.executeHandler(request);
48
+ }
49
+ async executeHandler(request) {
50
+ const hashIndex = request.handlerRef.indexOf("#");
51
+ const relPath = hashIndex > 0 ? request.handlerRef.slice(0, hashIndex) : request.handlerRef;
52
+ const handlerPath = relPath.startsWith("/") ? relPath : path.resolve(request.pluginRoot, relPath);
53
+ const platform = this.opts.platform ?? createNoopPlatform();
54
+ const runResult = await runInProcess({
55
+ descriptor: request.descriptor,
56
+ platform,
57
+ ui: noopUI,
58
+ handlerPath,
59
+ input: request.input,
60
+ cwd: request.pluginRoot
61
+ });
62
+ return runResult.data;
63
+ }
64
+ };
65
+ function createNoopPlatform() {
66
+ return {
67
+ logger: {
68
+ info: (msg, meta) => console.log("[handler]", msg, meta ?? ""),
69
+ warn: (msg, meta) => console.warn("[handler]", msg, meta ?? ""),
70
+ error: (msg, meta) => console.error("[handler]", msg, meta ?? ""),
71
+ debug: () => {
72
+ },
73
+ child: () => ({
74
+ info: (msg, meta) => console.log("[handler]", msg, meta ?? ""),
75
+ warn: (msg, meta) => console.warn("[handler]", msg, meta ?? ""),
76
+ error: (msg, meta) => console.error("[handler]", msg, meta ?? ""),
77
+ debug: () => {
78
+ },
79
+ child: () => ({})
80
+ })
81
+ },
82
+ cache: {
83
+ get: async () => null,
84
+ set: async () => {
85
+ },
86
+ delete: async () => {
87
+ },
88
+ clear: async () => {
89
+ }
90
+ },
91
+ config: {},
92
+ shell: {
93
+ exec: async () => {
94
+ throw new Error("shell.exec not available in runtime-server noop platform");
95
+ }
96
+ }
97
+ };
98
+ }
99
+
100
+ // src/cli.ts
101
+ var gatewayUrl = process.env["GATEWAY_WS_URL"];
102
+ var hostId = process.env["RUNTIME_HOST_ID"];
103
+ var token = process.env["GATEWAY_TOKEN"];
104
+ if (!gatewayUrl) {
105
+ process.stderr.write("[runtime-server] FATAL: GATEWAY_WS_URL is required\n");
106
+ process.exit(1);
107
+ }
108
+ if (!hostId) {
109
+ process.stderr.write("[runtime-server] FATAL: RUNTIME_HOST_ID is required\n");
110
+ process.exit(1);
111
+ }
112
+ if (!token) {
113
+ process.stderr.write("[runtime-server] FATAL: GATEWAY_TOKEN is required\n");
114
+ process.exit(1);
115
+ }
116
+ var server = new RuntimeServer({
117
+ gatewayUrl,
118
+ getAccessToken: () => token,
119
+ agentVersion: "0.1.0"
120
+ });
121
+ process.on("SIGTERM", () => {
122
+ server.stop();
123
+ process.exit(0);
124
+ });
125
+ process.on("SIGINT", () => {
126
+ server.stop();
127
+ process.exit(0);
128
+ });
129
+ server.start().catch((err) => {
130
+ process.stderr.write(`[runtime-server] Failed to start: ${err instanceof Error ? err.message : String(err)}
131
+ `);
132
+ process.exit(1);
133
+ });
134
+ //# sourceMappingURL=cli.js.map
135
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime-server.ts","../src/cli.ts"],"names":[],"mappings":"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","file":"cli.js","sourcesContent":["/**\n * @module @kb-labs/gateway-runtime-server\n *\n * RuntimeServer — plugin execution host that connects to Gateway as a Host Agent.\n *\n * Connects via WS, registers as adapter 'execution', and handles:\n * call { adapter: 'execution', method: 'execute', args: [RemoteExecutionRequest] }\n * → runInProcess(request)\n * → chunk { data: result } + result { done: true }\n *\n * Used for isolation: strict — runs inside a container alongside the workspace.\n *\n * Design doc: docs/architecture/execution-isolation.md\n * Architecture decision: WS/Gateway transport instead of TCP (see ADR context).\n */\n\nimport * as path from 'node:path';\nimport { GatewayClient } from '@kb-labs/host-agent-core';\nimport type { GatewayClientOptions } from '@kb-labs/host-agent-core';\nimport type { CapabilityCall } from '@kb-labs/host-agent-contracts';\nimport { runInProcess } from '@kb-labs/plugin-runtime';\nimport { noopUI } from '@kb-labs/plugin-contracts';\n\n/**\n * Minimal execution request as received from RemoteBackend via Gateway dispatch.\n */\ninterface RemoteExecutionRequest {\n executionId: string;\n handlerRef: string; // absolute path inside container, e.g. /workspace/dist/handler.js\n pluginRoot: string; // base dir, e.g. /workspace\n input: unknown;\n descriptor: unknown;\n timeoutMs?: number;\n context?: unknown;\n}\n\nexport interface RuntimeServerOptions {\n /** WebSocket URL of the Gateway, e.g. http://localhost:4000 */\n gatewayUrl: string;\n /** Access token for WS auth */\n getAccessToken: () => string;\n /** Called when refresh is needed (reconnect path) */\n onTokenExpired?: () => Promise<void>;\n /** Agent version string */\n agentVersion?: string;\n /**\n * Platform services for runInProcess.\n * If omitted, a minimal noop platform is used (for container environments\n * where full platform is not available).\n */\n platform?: unknown;\n}\n\nexport class RuntimeServer {\n private readonly client: GatewayClient;\n private hostId: string | null = null;\n\n constructor(private readonly opts: RuntimeServerOptions) {\n const clientOpts: GatewayClientOptions = {\n gatewayUrl: opts.gatewayUrl,\n agentVersion: opts.agentVersion ?? '0.1.0',\n getAccessToken: opts.getAccessToken,\n onTokenExpired: opts.onTokenExpired,\n capabilities: ['execution'],\n onConnected: (hId, sessionId) => {\n this.hostId = hId;\n console.log(`[runtime-server] Connected to Gateway: hostId=${hId} session=${sessionId}`);\n },\n onDisconnected: () => {\n console.log('[runtime-server] Disconnected from Gateway, reconnecting...');\n },\n };\n\n this.client = new GatewayClient(clientOpts);\n this.client.registerHandler('execution', (call) => this.handleExecution(call));\n }\n\n async start(): Promise<void> {\n await this.client.connect();\n }\n\n stop(): void {\n this.client.stop();\n }\n\n /** The hostId assigned by Gateway after handshake (null before connected) */\n getHostId(): string | null {\n return this.hostId;\n }\n\n private async handleExecution(call: CapabilityCall): Promise<unknown> {\n if (call.method !== 'execute') {\n throw new Error(`Unknown execution method: ${call.method}`);\n }\n\n const request = call.args[0] as RemoteExecutionRequest | undefined;\n if (!request || !request.handlerRef || !request.pluginRoot) {\n throw new Error('Missing required fields: handlerRef, pluginRoot');\n }\n\n return this.executeHandler(request);\n }\n\n private async executeHandler(request: RemoteExecutionRequest): Promise<unknown> {\n // Resolve absolute handler path\n const hashIndex = request.handlerRef.indexOf('#');\n const relPath = hashIndex > 0 ? request.handlerRef.slice(0, hashIndex) : request.handlerRef;\n\n // handlerRef may be absolute (/workspace/...) or relative to pluginRoot\n const handlerPath = relPath.startsWith('/')\n ? relPath\n : path.resolve(request.pluginRoot, relPath);\n\n const platform = this.opts.platform ?? createNoopPlatform();\n\n const runResult = await runInProcess({\n descriptor: request.descriptor as Parameters<typeof runInProcess>[0]['descriptor'],\n platform: platform as Parameters<typeof runInProcess>[0]['platform'],\n ui: noopUI,\n handlerPath,\n input: request.input,\n cwd: request.pluginRoot,\n });\n\n return runResult.data;\n }\n}\n\n/**\n * Minimal noop platform for container environments.\n * Logger writes to stdout, cache is a no-op.\n * Production use should pass real platform services.\n */\nfunction createNoopPlatform(): unknown {\n return {\n logger: {\n info: (msg: string, meta?: unknown) => console.log('[handler]', msg, meta ?? ''),\n warn: (msg: string, meta?: unknown) => console.warn('[handler]', msg, meta ?? ''),\n error: (msg: string, meta?: unknown) => console.error('[handler]', msg, meta ?? ''),\n debug: () => {},\n child: () => ({\n info: (msg: string, meta?: unknown) => console.log('[handler]', msg, meta ?? ''),\n warn: (msg: string, meta?: unknown) => console.warn('[handler]', msg, meta ?? ''),\n error: (msg: string, meta?: unknown) => console.error('[handler]', msg, meta ?? ''),\n debug: () => {},\n child: () => ({}),\n }),\n },\n cache: {\n get: async () => null,\n set: async () => {},\n delete: async () => {},\n clear: async () => {},\n },\n config: {},\n shell: {\n exec: async () => { throw new Error('shell.exec not available in runtime-server noop platform'); },\n },\n };\n}\n","/**\n * @module @kb-labs/gateway-runtime-server/cli\n *\n * CLI entry point for the runtime server.\n * Run inside an isolation: strict container to handle remote plugin execution.\n *\n * Required env vars:\n * GATEWAY_WS_URL — WebSocket URL of the Gateway (e.g. ws://gateway:4000)\n * RUNTIME_HOST_ID — Deterministic hostId assigned by workflow-daemon (e.g. runtime-abc123)\n * GATEWAY_TOKEN — Bearer token for WS auth\n */\n\nimport { RuntimeServer } from './runtime-server.js';\n\nconst gatewayUrl = process.env['GATEWAY_WS_URL'];\nconst hostId = process.env['RUNTIME_HOST_ID'];\nconst token = process.env['GATEWAY_TOKEN'];\n\nif (!gatewayUrl) {\n process.stderr.write('[runtime-server] FATAL: GATEWAY_WS_URL is required\\n');\n process.exit(1);\n}\nif (!hostId) {\n process.stderr.write('[runtime-server] FATAL: RUNTIME_HOST_ID is required\\n');\n process.exit(1);\n}\nif (!token) {\n process.stderr.write('[runtime-server] FATAL: GATEWAY_TOKEN is required\\n');\n process.exit(1);\n}\n\nconst server = new RuntimeServer({\n gatewayUrl,\n getAccessToken: () => token,\n agentVersion: '0.1.0',\n});\n\nprocess.on('SIGTERM', () => {\n server.stop();\n process.exit(0);\n});\n\nprocess.on('SIGINT', () => {\n server.stop();\n process.exit(0);\n});\n\nserver.start().catch((err: unknown) => {\n process.stderr.write(`[runtime-server] Failed to start: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exit(1);\n});\n"]}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @module @kb-labs/gateway-runtime-server
3
+ *
4
+ * RuntimeServer — plugin execution host that connects to Gateway as a Host Agent.
5
+ *
6
+ * Connects via WS, registers as adapter 'execution', and handles:
7
+ * call { adapter: 'execution', method: 'execute', args: [RemoteExecutionRequest] }
8
+ * → runInProcess(request)
9
+ * → chunk { data: result } + result { done: true }
10
+ *
11
+ * Used for isolation: strict — runs inside a container alongside the workspace.
12
+ *
13
+ * Design doc: docs/architecture/execution-isolation.md
14
+ * Architecture decision: WS/Gateway transport instead of TCP (see ADR context).
15
+ */
16
+ interface RuntimeServerOptions {
17
+ /** WebSocket URL of the Gateway, e.g. http://localhost:4000 */
18
+ gatewayUrl: string;
19
+ /** Access token for WS auth */
20
+ getAccessToken: () => string;
21
+ /** Called when refresh is needed (reconnect path) */
22
+ onTokenExpired?: () => Promise<void>;
23
+ /** Agent version string */
24
+ agentVersion?: string;
25
+ /**
26
+ * Platform services for runInProcess.
27
+ * If omitted, a minimal noop platform is used (for container environments
28
+ * where full platform is not available).
29
+ */
30
+ platform?: unknown;
31
+ }
32
+ declare class RuntimeServer {
33
+ private readonly opts;
34
+ private readonly client;
35
+ private hostId;
36
+ constructor(opts: RuntimeServerOptions);
37
+ start(): Promise<void>;
38
+ stop(): void;
39
+ /** The hostId assigned by Gateway after handshake (null before connected) */
40
+ getHostId(): string | null;
41
+ private handleExecution;
42
+ private executeHandler;
43
+ }
44
+
45
+ export { RuntimeServer, type RuntimeServerOptions };
package/dist/index.js ADDED
@@ -0,0 +1,102 @@
1
+ import * as path from 'path';
2
+ import { GatewayClient } from '@kb-labs/host-agent-core';
3
+ import { runInProcess } from '@kb-labs/plugin-runtime';
4
+ import { noopUI } from '@kb-labs/plugin-contracts';
5
+
6
+ // src/runtime-server.ts
7
+ var RuntimeServer = class {
8
+ constructor(opts) {
9
+ this.opts = opts;
10
+ const clientOpts = {
11
+ gatewayUrl: opts.gatewayUrl,
12
+ agentVersion: opts.agentVersion ?? "0.1.0",
13
+ getAccessToken: opts.getAccessToken,
14
+ onTokenExpired: opts.onTokenExpired,
15
+ capabilities: ["execution"],
16
+ onConnected: (hId, sessionId) => {
17
+ this.hostId = hId;
18
+ console.log(`[runtime-server] Connected to Gateway: hostId=${hId} session=${sessionId}`);
19
+ },
20
+ onDisconnected: () => {
21
+ console.log("[runtime-server] Disconnected from Gateway, reconnecting...");
22
+ }
23
+ };
24
+ this.client = new GatewayClient(clientOpts);
25
+ this.client.registerHandler("execution", (call) => this.handleExecution(call));
26
+ }
27
+ client;
28
+ hostId = null;
29
+ async start() {
30
+ await this.client.connect();
31
+ }
32
+ stop() {
33
+ this.client.stop();
34
+ }
35
+ /** The hostId assigned by Gateway after handshake (null before connected) */
36
+ getHostId() {
37
+ return this.hostId;
38
+ }
39
+ async handleExecution(call) {
40
+ if (call.method !== "execute") {
41
+ throw new Error(`Unknown execution method: ${call.method}`);
42
+ }
43
+ const request = call.args[0];
44
+ if (!request || !request.handlerRef || !request.pluginRoot) {
45
+ throw new Error("Missing required fields: handlerRef, pluginRoot");
46
+ }
47
+ return this.executeHandler(request);
48
+ }
49
+ async executeHandler(request) {
50
+ const hashIndex = request.handlerRef.indexOf("#");
51
+ const relPath = hashIndex > 0 ? request.handlerRef.slice(0, hashIndex) : request.handlerRef;
52
+ const handlerPath = relPath.startsWith("/") ? relPath : path.resolve(request.pluginRoot, relPath);
53
+ const platform = this.opts.platform ?? createNoopPlatform();
54
+ const runResult = await runInProcess({
55
+ descriptor: request.descriptor,
56
+ platform,
57
+ ui: noopUI,
58
+ handlerPath,
59
+ input: request.input,
60
+ cwd: request.pluginRoot
61
+ });
62
+ return runResult.data;
63
+ }
64
+ };
65
+ function createNoopPlatform() {
66
+ return {
67
+ logger: {
68
+ info: (msg, meta) => console.log("[handler]", msg, meta ?? ""),
69
+ warn: (msg, meta) => console.warn("[handler]", msg, meta ?? ""),
70
+ error: (msg, meta) => console.error("[handler]", msg, meta ?? ""),
71
+ debug: () => {
72
+ },
73
+ child: () => ({
74
+ info: (msg, meta) => console.log("[handler]", msg, meta ?? ""),
75
+ warn: (msg, meta) => console.warn("[handler]", msg, meta ?? ""),
76
+ error: (msg, meta) => console.error("[handler]", msg, meta ?? ""),
77
+ debug: () => {
78
+ },
79
+ child: () => ({})
80
+ })
81
+ },
82
+ cache: {
83
+ get: async () => null,
84
+ set: async () => {
85
+ },
86
+ delete: async () => {
87
+ },
88
+ clear: async () => {
89
+ }
90
+ },
91
+ config: {},
92
+ shell: {
93
+ exec: async () => {
94
+ throw new Error("shell.exec not available in runtime-server noop platform");
95
+ }
96
+ }
97
+ };
98
+ }
99
+
100
+ export { RuntimeServer };
101
+ //# sourceMappingURL=index.js.map
102
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime-server.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/gateway-runtime-server\n *\n * RuntimeServer — plugin execution host that connects to Gateway as a Host Agent.\n *\n * Connects via WS, registers as adapter 'execution', and handles:\n * call { adapter: 'execution', method: 'execute', args: [RemoteExecutionRequest] }\n * → runInProcess(request)\n * → chunk { data: result } + result { done: true }\n *\n * Used for isolation: strict — runs inside a container alongside the workspace.\n *\n * Design doc: docs/architecture/execution-isolation.md\n * Architecture decision: WS/Gateway transport instead of TCP (see ADR context).\n */\n\nimport * as path from 'node:path';\nimport { GatewayClient } from '@kb-labs/host-agent-core';\nimport type { GatewayClientOptions } from '@kb-labs/host-agent-core';\nimport type { CapabilityCall } from '@kb-labs/host-agent-contracts';\nimport { runInProcess } from '@kb-labs/plugin-runtime';\nimport { noopUI } from '@kb-labs/plugin-contracts';\n\n/**\n * Minimal execution request as received from RemoteBackend via Gateway dispatch.\n */\ninterface RemoteExecutionRequest {\n executionId: string;\n handlerRef: string; // absolute path inside container, e.g. /workspace/dist/handler.js\n pluginRoot: string; // base dir, e.g. /workspace\n input: unknown;\n descriptor: unknown;\n timeoutMs?: number;\n context?: unknown;\n}\n\nexport interface RuntimeServerOptions {\n /** WebSocket URL of the Gateway, e.g. http://localhost:4000 */\n gatewayUrl: string;\n /** Access token for WS auth */\n getAccessToken: () => string;\n /** Called when refresh is needed (reconnect path) */\n onTokenExpired?: () => Promise<void>;\n /** Agent version string */\n agentVersion?: string;\n /**\n * Platform services for runInProcess.\n * If omitted, a minimal noop platform is used (for container environments\n * where full platform is not available).\n */\n platform?: unknown;\n}\n\nexport class RuntimeServer {\n private readonly client: GatewayClient;\n private hostId: string | null = null;\n\n constructor(private readonly opts: RuntimeServerOptions) {\n const clientOpts: GatewayClientOptions = {\n gatewayUrl: opts.gatewayUrl,\n agentVersion: opts.agentVersion ?? '0.1.0',\n getAccessToken: opts.getAccessToken,\n onTokenExpired: opts.onTokenExpired,\n capabilities: ['execution'],\n onConnected: (hId, sessionId) => {\n this.hostId = hId;\n console.log(`[runtime-server] Connected to Gateway: hostId=${hId} session=${sessionId}`);\n },\n onDisconnected: () => {\n console.log('[runtime-server] Disconnected from Gateway, reconnecting...');\n },\n };\n\n this.client = new GatewayClient(clientOpts);\n this.client.registerHandler('execution', (call) => this.handleExecution(call));\n }\n\n async start(): Promise<void> {\n await this.client.connect();\n }\n\n stop(): void {\n this.client.stop();\n }\n\n /** The hostId assigned by Gateway after handshake (null before connected) */\n getHostId(): string | null {\n return this.hostId;\n }\n\n private async handleExecution(call: CapabilityCall): Promise<unknown> {\n if (call.method !== 'execute') {\n throw new Error(`Unknown execution method: ${call.method}`);\n }\n\n const request = call.args[0] as RemoteExecutionRequest | undefined;\n if (!request || !request.handlerRef || !request.pluginRoot) {\n throw new Error('Missing required fields: handlerRef, pluginRoot');\n }\n\n return this.executeHandler(request);\n }\n\n private async executeHandler(request: RemoteExecutionRequest): Promise<unknown> {\n // Resolve absolute handler path\n const hashIndex = request.handlerRef.indexOf('#');\n const relPath = hashIndex > 0 ? request.handlerRef.slice(0, hashIndex) : request.handlerRef;\n\n // handlerRef may be absolute (/workspace/...) or relative to pluginRoot\n const handlerPath = relPath.startsWith('/')\n ? relPath\n : path.resolve(request.pluginRoot, relPath);\n\n const platform = this.opts.platform ?? createNoopPlatform();\n\n const runResult = await runInProcess({\n descriptor: request.descriptor as Parameters<typeof runInProcess>[0]['descriptor'],\n platform: platform as Parameters<typeof runInProcess>[0]['platform'],\n ui: noopUI,\n handlerPath,\n input: request.input,\n cwd: request.pluginRoot,\n });\n\n return runResult.data;\n }\n}\n\n/**\n * Minimal noop platform for container environments.\n * Logger writes to stdout, cache is a no-op.\n * Production use should pass real platform services.\n */\nfunction createNoopPlatform(): unknown {\n return {\n logger: {\n info: (msg: string, meta?: unknown) => console.log('[handler]', msg, meta ?? ''),\n warn: (msg: string, meta?: unknown) => console.warn('[handler]', msg, meta ?? ''),\n error: (msg: string, meta?: unknown) => console.error('[handler]', msg, meta ?? ''),\n debug: () => {},\n child: () => ({\n info: (msg: string, meta?: unknown) => console.log('[handler]', msg, meta ?? ''),\n warn: (msg: string, meta?: unknown) => console.warn('[handler]', msg, meta ?? ''),\n error: (msg: string, meta?: unknown) => console.error('[handler]', msg, meta ?? ''),\n debug: () => {},\n child: () => ({}),\n }),\n },\n cache: {\n get: async () => null,\n set: async () => {},\n delete: async () => {},\n clear: async () => {},\n },\n config: {},\n shell: {\n exec: async () => { throw new Error('shell.exec not available in runtime-server noop platform'); },\n },\n };\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@kb-labs/gateway-runtime-server",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "kb-runtime-server": "./dist/cli.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "scripts": {
21
+ "clean": "rimraf dist",
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "type-check": "tsc --noEmit",
25
+ "lint": "eslint .",
26
+ "lint:fix": "eslint . --fix",
27
+ "test": "vitest run -c ../../vitest.config.ts",
28
+ "test:watch": "vitest -c ../../vitest.config.ts",
29
+ "build:docker": "DOCKER_BUILD=1 tsup",
30
+ "docker:build": "pnpm run build:docker && docker build -t kb-runtime-server:local ."
31
+ },
32
+ "dependencies": {
33
+ "@kb-labs/host-agent-core": "^0.1.0",
34
+ "@kb-labs/host-agent-contracts": "^0.1.0",
35
+ "@kb-labs/plugin-runtime": "^1.3.0",
36
+ "@kb-labs/plugin-contracts": "^1.3.0"
37
+ },
38
+ "devDependencies": {
39
+ "@kb-labs/devkit": "link:../../../kb-labs-devkit",
40
+ "@types/node": "^22.0.0",
41
+ "tsup": "^8.5.0",
42
+ "vitest": "^3.2.4"
43
+ }
44
+ }