@mmmbuto/nexuscli 0.9.7004-termux → 0.10.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +89 -158
  3. package/bin/nexuscli.js +12 -0
  4. package/frontend/dist/assets/{index-D8XkscmI.js → index-Bztt9hew.js} +1704 -1704
  5. package/frontend/dist/assets/{index-CoLEGBO4.css → index-Dj7jz2fy.css} +1 -1
  6. package/frontend/dist/index.html +2 -2
  7. package/frontend/dist/sw.js +1 -1
  8. package/lib/cli/api.js +19 -1
  9. package/lib/cli/config.js +27 -5
  10. package/lib/cli/engines.js +84 -202
  11. package/lib/cli/init.js +56 -2
  12. package/lib/cli/model.js +17 -7
  13. package/lib/cli/start.js +37 -24
  14. package/lib/cli/stop.js +12 -41
  15. package/lib/cli/update.js +28 -0
  16. package/lib/cli/workspaces.js +4 -0
  17. package/lib/config/manager.js +112 -8
  18. package/lib/config/models.js +388 -192
  19. package/lib/server/db/migrations/001_ultra_light_schema.sql +1 -1
  20. package/lib/server/db/migrations/006_runtime_lane_tracking.sql +79 -0
  21. package/lib/server/lib/getPty.js +51 -0
  22. package/lib/server/lib/pty-adapter.js +101 -57
  23. package/lib/server/lib/pty-provider.js +63 -0
  24. package/lib/server/lib/pty-utils-loader.js +136 -0
  25. package/lib/server/middleware/auth.js +27 -4
  26. package/lib/server/models/Conversation.js +7 -3
  27. package/lib/server/models/Message.js +29 -5
  28. package/lib/server/routes/chat.js +27 -4
  29. package/lib/server/routes/codex.js +35 -8
  30. package/lib/server/routes/config.js +9 -1
  31. package/lib/server/routes/gemini.js +24 -5
  32. package/lib/server/routes/jobs.js +15 -156
  33. package/lib/server/routes/models.js +12 -10
  34. package/lib/server/routes/qwen.js +26 -7
  35. package/lib/server/routes/runtimes.js +68 -0
  36. package/lib/server/server.js +3 -0
  37. package/lib/server/services/claude-wrapper.js +60 -62
  38. package/lib/server/services/cli-loader.js +1 -1
  39. package/lib/server/services/codex-wrapper.js +79 -10
  40. package/lib/server/services/gemini-wrapper.js +9 -4
  41. package/lib/server/services/job-runner.js +156 -0
  42. package/lib/server/services/qwen-wrapper.js +26 -11
  43. package/lib/server/services/runtime-manager.js +467 -0
  44. package/lib/server/services/session-importer.js +6 -1
  45. package/lib/server/services/session-manager.js +56 -14
  46. package/lib/server/services/workspace-manager.js +121 -0
  47. package/lib/server/tests/integration.test.js +12 -0
  48. package/lib/server/tests/runtime-manager.test.js +46 -0
  49. package/lib/server/tests/runtime-persistence.test.js +97 -0
  50. package/lib/setup/postinstall-pty-check.js +183 -0
  51. package/lib/setup/postinstall.js +60 -41
  52. package/lib/utils/restart-warning.js +18 -0
  53. package/lib/utils/server.js +88 -0
  54. package/lib/utils/termux.js +1 -1
  55. package/lib/utils/update-check.js +153 -0
  56. package/lib/utils/update-runner.js +62 -0
  57. package/package.json +6 -5
@@ -0,0 +1,79 @@
1
+ ALTER TABLE sessions ADD COLUMN lane TEXT;
2
+
3
+ ALTER TABLE sessions ADD COLUMN runtime_id TEXT;
4
+
5
+ ALTER TABLE sessions ADD COLUMN provider_id TEXT;
6
+
7
+ ALTER TABLE sessions ADD COLUMN model_id TEXT;
8
+
9
+ ALTER TABLE messages ADD COLUMN lane TEXT;
10
+
11
+ ALTER TABLE messages ADD COLUMN runtime_id TEXT;
12
+
13
+ ALTER TABLE messages ADD COLUMN provider_id TEXT;
14
+
15
+ ALTER TABLE messages ADD COLUMN model_id TEXT;
16
+
17
+ UPDATE sessions
18
+ SET engine = 'claude'
19
+ WHERE lower(engine) = 'claude-code';
20
+
21
+ UPDATE messages
22
+ SET engine = 'claude'
23
+ WHERE lower(engine) = 'claude-code';
24
+
25
+ UPDATE sessions
26
+ SET
27
+ lane = COALESCE(lane, 'native'),
28
+ runtime_id = COALESCE(runtime_id,
29
+ CASE
30
+ WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'codex-native'
31
+ WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'gemini-native'
32
+ WHEN lower(engine) LIKE '%qwen%' THEN 'qwen-native'
33
+ ELSE 'claude-native'
34
+ END
35
+ ),
36
+ provider_id = COALESCE(provider_id,
37
+ CASE
38
+ WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'openai'
39
+ WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'google'
40
+ WHEN lower(engine) LIKE '%qwen%' THEN 'qwen'
41
+ ELSE 'anthropic'
42
+ END
43
+ ),
44
+ model_id = COALESCE(model_id,
45
+ CASE
46
+ WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'gpt-5.3-codex'
47
+ WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'gemini-3-pro-preview'
48
+ WHEN lower(engine) LIKE '%qwen%' THEN 'qwen3-coder-plus'
49
+ ELSE 'sonnet'
50
+ END
51
+ );
52
+
53
+ UPDATE messages
54
+ SET
55
+ lane = COALESCE(lane, 'native'),
56
+ runtime_id = COALESCE(runtime_id,
57
+ CASE
58
+ WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'codex-native'
59
+ WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'gemini-native'
60
+ WHEN lower(engine) LIKE '%qwen%' THEN 'qwen-native'
61
+ ELSE 'claude-native'
62
+ END
63
+ ),
64
+ provider_id = COALESCE(provider_id,
65
+ CASE
66
+ WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'openai'
67
+ WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'google'
68
+ WHEN lower(engine) LIKE '%qwen%' THEN 'qwen'
69
+ ELSE 'anthropic'
70
+ END
71
+ ),
72
+ model_id = COALESCE(model_id,
73
+ CASE
74
+ WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'gpt-5.3-codex'
75
+ WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'gemini-3-pro-preview'
76
+ WHEN lower(engine) LIKE '%qwen%' THEN 'qwen3-coder-plus'
77
+ ELSE 'sonnet'
78
+ END
79
+ );
@@ -0,0 +1,51 @@
1
+ /**
2
+ * PTY Loader for NexusCLI (legacy compatibility wrapper)
3
+ * Delegates to @mmmbuto/pty-termux-utils multi-provider strategy.
4
+ *
5
+ * Provider priority:
6
+ * 1. Termux: @mmmbuto/node-pty-android-arm64
7
+ * 2. Linux ARM64: @lydell/node-pty-linux-arm64
8
+ * 3. Fallback: child_process adapter (pty-adapter.js)
9
+ *
10
+ * @version 1.1.0
11
+ */
12
+
13
+ const { getPtyFn, getLogDebugFn } = require('./pty-utils-loader');
14
+
15
+ const getSharedPty = getPtyFn();
16
+ const logDebug = getLogDebugFn();
17
+ const tryRequire = (moduleName) => {
18
+ try {
19
+ require(moduleName);
20
+ return true;
21
+ } catch (_) {
22
+ return false;
23
+ }
24
+ };
25
+
26
+ module.exports = {
27
+ /**
28
+ * Get PTY implementation with fallback
29
+ * @returns {Promise<{module: any, name: string}|null>}
30
+ */
31
+ async getPty() {
32
+ const pty = await getSharedPty();
33
+ if (pty) {
34
+ logDebug(`Using native PTY provider: ${pty.name}`);
35
+ return pty;
36
+ }
37
+ logDebug('Native PTY not available, using fallback adapter');
38
+ return null;
39
+ },
40
+
41
+ /**
42
+ * Synchronous check if PTY is available
43
+ * @returns {boolean}
44
+ */
45
+ isPtyAvailable() {
46
+ return (
47
+ tryRequire('@mmmbuto/node-pty-android-arm64') ||
48
+ tryRequire('@lydell/node-pty-linux-arm64')
49
+ );
50
+ }
51
+ };
@@ -1,81 +1,125 @@
1
1
  /**
2
- * PTY Adapter for NexusCLI (Termux)
3
- * Provides node-pty-like interface using child_process.spawn
4
- * Termux-only: no native node-pty compilation needed
2
+ * PTY Adapter for NexusCLI (Termux + Linux ARM64)
3
+ * Wrapper around @mmmbuto/pty-termux-utils shared library
4
+ * Provides node-pty-like interface with graceful fallback
5
5
  *
6
- * @version 0.5.0 - Added stdin support for interrupt (ESC key)
6
+ * @version 1.0.0
7
7
  */
8
8
 
9
- const { spawn: cpSpawn } = require('child_process');
9
+ const { getPtyFn, getFallbackAdapter } = require('./pty-utils-loader');
10
+
11
+ const getSharedPty = getPtyFn();
12
+ const tryRequire = (moduleName) => {
13
+ try {
14
+ require(moduleName);
15
+ return true;
16
+ } catch (_) {
17
+ return false;
18
+ }
19
+ };
20
+
21
+ // Create shared fallback adapter (always uses child_process)
22
+ const fallbackAdapter = getFallbackAdapter();
23
+
24
+ // Cache for native PTY (sync access)
25
+ let cachedNativePty = null;
26
+
27
+ /**
28
+ * Initialize native PTY (called at startup)
29
+ */
30
+ async function initNativePty() {
31
+ if (cachedNativePty !== null) {
32
+ return cachedNativePty;
33
+ }
34
+
35
+ try {
36
+ cachedNativePty = await getSharedPty();
37
+ return cachedNativePty;
38
+ } catch (e) {
39
+ cachedNativePty = false; // Error loading
40
+ return null;
41
+ }
42
+ }
10
43
 
11
44
  /**
12
- * Spawn a process with node-pty-like interface
45
+ * Synchronous spawn (for compatibility with existing wrapper code)
46
+ * Uses fallback adapter for simplicity (no native PTY in sync mode)
13
47
  * @param {string} command - Command to spawn
14
48
  * @param {string[]} args - Arguments
15
49
  * @param {Object} options - Spawn options (cwd, env)
16
- * @returns {Object} PTY-like interface with onData, onExit, kill
50
+ * @returns {Object} PTY-like interface
17
51
  */
18
52
  function spawn(command, args, options = {}) {
19
- const proc = cpSpawn(command, args, {
20
- cwd: options.cwd,
21
- env: options.env,
22
- shell: false,
23
- stdio: ['pipe', 'pipe', 'pipe'], // stdin enabled for interrupt support
24
- });
25
-
26
- const dataHandlers = [];
27
- const exitHandlers = [];
28
- const errorHandlers = [];
53
+ return fallbackAdapter.spawn(command, args, options);
54
+ }
29
55
 
30
- proc.stdout.on('data', (buf) => {
31
- const data = buf.toString();
32
- console.log('[PTY-Adapter] stdout:', data.substring(0, 200));
33
- dataHandlers.forEach((fn) => fn(data));
34
- });
56
+ /**
57
+ * Async spawn with native PTY support
58
+ * @param {string} command - Command to spawn
59
+ * @param {string[]} args - Arguments
60
+ * @param {Object} options - Spawn options (cwd, env)
61
+ * @returns {Promise<Object>} PTY-like interface
62
+ */
63
+ async function spawnAsync(command, args, options = {}) {
64
+ const pty = await initNativePty();
65
+
66
+ if (pty) {
67
+ // Use native PTY
68
+ const proc = pty.module.spawn(command, args, {
69
+ cols: options.cols || 80,
70
+ rows: options.rows || 24,
71
+ cwd: options.cwd,
72
+ env: options.env,
73
+ });
35
74
 
36
- proc.stderr.on('data', (buf) => {
37
- const data = buf.toString();
38
- console.log('[PTY-Adapter] stderr:', data.substring(0, 200));
39
- dataHandlers.forEach((fn) => fn(data));
40
- });
75
+ const dataHandlers = [];
76
+ const exitHandlers = [];
41
77
 
42
- proc.on('close', (code) => {
43
- exitHandlers.forEach((fn) => fn({ exitCode: code ?? 0 }));
44
- });
78
+ proc.on('data', (data) => {
79
+ dataHandlers.forEach((fn) => fn(data));
80
+ });
45
81
 
46
- proc.on('error', (err) => {
47
- console.error('[PTY-Adapter] Error:', err.message);
48
- errorHandlers.forEach((fn) => fn(err));
49
- });
82
+ proc.on('exit', (code, signal) => {
83
+ exitHandlers.forEach((fn) => fn({ exitCode: code, signal }));
84
+ });
50
85
 
51
- return {
52
- onData: (fn) => dataHandlers.push(fn),
53
- onExit: (fn) => exitHandlers.push(fn),
54
- onError: (fn) => errorHandlers.push(fn),
55
- write: (data) => proc.stdin?.writable && proc.stdin.write(data),
56
- /**
57
- * Send ESC key (0x1B) to interrupt CLI
58
- * Used to gracefully stop Claude/Gemini CLI execution
59
- * @returns {boolean} true if ESC was sent successfully
60
- */
61
- sendEsc: () => {
62
- if (proc.stdin?.writable) {
63
- proc.stdin.write('\x1B');
86
+ return {
87
+ onData: (fn) => dataHandlers.push(fn),
88
+ onExit: (fn) => exitHandlers.push(fn),
89
+ onError: (fn) => {}, // No error event in native PTY
90
+ write: (data) => proc.write(data),
91
+ sendEsc: () => {
92
+ proc.write('\x1B');
64
93
  return true;
65
- }
66
- return false;
67
- },
68
- kill: (signal = 'SIGTERM') => proc.kill(signal),
69
- pid: proc.pid,
70
- };
94
+ },
95
+ resize: (cols, rows) => proc.resize(cols, rows),
96
+ kill: (signal = 'SIGTERM') => proc.kill(signal),
97
+ pid: proc.pid,
98
+ };
99
+ } else {
100
+ // Use fallback adapter
101
+ return fallbackAdapter.spawn(command, args, options);
102
+ }
71
103
  }
72
104
 
73
105
  /**
74
- * Check if native PTY is available
75
- * Always returns false on Termux (we use spawn adapter)
106
+ * Synchronous check if PTY is available
107
+ * @returns {boolean}
76
108
  */
77
109
  function isPtyAvailable() {
78
- return false;
110
+ return (
111
+ tryRequire('@mmmbuto/node-pty-android-arm64') ||
112
+ tryRequire('@lydell/node-pty-linux-arm64')
113
+ );
79
114
  }
80
115
 
81
- module.exports = { spawn, isPtyAvailable };
116
+ // Initialize native PTY in background (non-blocking)
117
+ initNativePty().catch(() => {
118
+ // Silently ignore initialization errors
119
+ });
120
+
121
+ module.exports = {
122
+ spawn, // Sync spawn (uses fallback)
123
+ spawnAsync, // Async spawn (uses native PTY if available)
124
+ isPtyAvailable,
125
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * PTY Provider for NexusCLI (Termux + Linux ARM64)
3
+ * Provides PTY detection with fallback to child_process adapter
4
+ * Uses @mmmbuto/pty-termux-utils shared library
5
+ *
6
+ * @version 1.0.0
7
+ */
8
+
9
+ const {
10
+ getPtyFn,
11
+ getSpawnPtyFn,
12
+ getFallbackAdapter: getSafeFallbackAdapter
13
+ } = require('./pty-utils-loader');
14
+
15
+ const getSharedPty = getPtyFn();
16
+ const spawnPty = getSpawnPtyFn();
17
+ const tryRequire = (moduleName) => {
18
+ try {
19
+ require(moduleName);
20
+ return true;
21
+ } catch (_) {
22
+ return false;
23
+ }
24
+ };
25
+
26
+ /**
27
+ * Get PTY implementation with fallback
28
+ * @returns {Promise<{module: any, name: string}|null>}
29
+ */
30
+ async function getPty() {
31
+ const pty = await getSharedPty();
32
+ if (pty) {
33
+ return pty;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * Get fallback adapter (child_process)
40
+ * @returns {Object} Adapter with spawn method
41
+ */
42
+ function getFallbackAdapter() {
43
+ return getSafeFallbackAdapter();
44
+ }
45
+
46
+ /**
47
+ * Synchronous check if PTY is available
48
+ * @returns {boolean}
49
+ */
50
+ function isPtyAvailable() {
51
+ return (
52
+ tryRequire('@mmmbuto/node-pty-android-arm64') ||
53
+ tryRequire('@lydell/node-pty-linux-arm64')
54
+ );
55
+ }
56
+
57
+ module.exports = {
58
+ getPty,
59
+ getFallbackAdapter,
60
+ isPtyAvailable,
61
+ // Re-export from shared library
62
+ spawnPty,
63
+ };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Safe loader for @mmmbuto/pty-termux-utils with fallback adapter.
3
+ * Prevents hard crashes if the dependency is missing or broken.
4
+ */
5
+
6
+ const { spawn } = require('child_process');
7
+
8
+ let cachedUtils;
9
+ let warned = false;
10
+
11
+ function warnOnce(error) {
12
+ if (warned) return;
13
+ warned = true;
14
+ const message = error && error.message ? error.message : String(error);
15
+ console.warn('[PTY] Failed to load @mmmbuto/pty-termux-utils. Using fallback adapter.');
16
+ console.warn(`[PTY] Reason: ${message}`);
17
+ }
18
+
19
+ function loadPtyUtils() {
20
+ if (cachedUtils !== undefined) return cachedUtils;
21
+ try {
22
+ cachedUtils = require('@mmmbuto/pty-termux-utils');
23
+ } catch (err) {
24
+ warnOnce(err);
25
+ cachedUtils = null;
26
+ }
27
+ return cachedUtils;
28
+ }
29
+
30
+ function ensurePtyInterface(proc) {
31
+ if (!proc) return proc;
32
+ if (typeof proc.onData === 'function' && typeof proc.onExit === 'function') {
33
+ return proc;
34
+ }
35
+
36
+ const hasChildStd = !!(proc.stdout || proc.stderr);
37
+ const dataHandlers = [];
38
+ const exitHandlers = [];
39
+
40
+ if (typeof proc.on === 'function' && !hasChildStd) {
41
+ proc.on('data', (data) => {
42
+ dataHandlers.forEach((fn) => fn(data));
43
+ });
44
+ proc.on('exit', (code, signal) => {
45
+ exitHandlers.forEach((fn) => fn({ exitCode: code, signal }));
46
+ });
47
+ } else {
48
+ proc.stdout?.on('data', (data) => {
49
+ dataHandlers.forEach((fn) => fn(data.toString()));
50
+ });
51
+ proc.stderr?.on('data', (data) => {
52
+ dataHandlers.forEach((fn) => fn(data.toString()));
53
+ });
54
+ proc.on?.('exit', (code, signal) => {
55
+ exitHandlers.forEach((fn) => fn({ exitCode: code ?? -1, signal }));
56
+ });
57
+ }
58
+
59
+ proc.onData = (fn) => dataHandlers.push(fn);
60
+ proc.onExit = (fn) => exitHandlers.push(fn);
61
+
62
+ if (typeof proc.onError !== 'function' && typeof proc.on === 'function') {
63
+ proc.onError = (fn) => proc.on('error', fn);
64
+ }
65
+ if (typeof proc.write !== 'function') {
66
+ proc.write = (data) => proc.stdin?.write(data);
67
+ }
68
+ if (typeof proc.sendEsc !== 'function') {
69
+ proc.sendEsc = () => {
70
+ if (proc.stdin?.writable) {
71
+ proc.stdin.write('\x1B');
72
+ return true;
73
+ }
74
+ return false;
75
+ };
76
+ }
77
+ if (typeof proc.resize !== 'function') {
78
+ proc.resize = () => {};
79
+ }
80
+ if (typeof proc.kill !== 'function' && proc.process?.kill) {
81
+ proc.kill = (signal = 'SIGTERM') => proc.process.kill(signal);
82
+ }
83
+ if (proc.pid == null && proc.process?.pid != null) {
84
+ proc.pid = proc.process.pid;
85
+ }
86
+
87
+ return proc;
88
+ }
89
+
90
+ function createLocalFallbackAdapter() {
91
+ return {
92
+ spawn: (file, args, options = {}) => {
93
+ const child = spawn(file, args, {
94
+ cwd: options.cwd,
95
+ env: options.env ? { ...process.env, ...options.env } : undefined,
96
+ });
97
+ return ensurePtyInterface(child);
98
+ }
99
+ };
100
+ }
101
+
102
+ function getPtyFn() {
103
+ const utils = loadPtyUtils();
104
+ return utils?.getPty ? utils.getPty : async () => null;
105
+ }
106
+
107
+ function getSpawnPtyFn() {
108
+ const utils = loadPtyUtils();
109
+ return utils?.spawnPty
110
+ ? utils.spawnPty
111
+ : async () => { throw new Error('Native PTY utilities unavailable'); };
112
+ }
113
+
114
+ function getLogDebugFn() {
115
+ const utils = loadPtyUtils();
116
+ return utils?.logDebug ? utils.logDebug : () => {};
117
+ }
118
+
119
+ function getFallbackAdapter() {
120
+ const utils = loadPtyUtils();
121
+ if (utils?.createFallbackAdapter) {
122
+ const base = utils.createFallbackAdapter();
123
+ return {
124
+ spawn: (...args) => ensurePtyInterface(base.spawn(...args))
125
+ };
126
+ }
127
+ return createLocalFallbackAdapter();
128
+ }
129
+
130
+ module.exports = {
131
+ getPtyFn,
132
+ getSpawnPtyFn,
133
+ getLogDebugFn,
134
+ getFallbackAdapter,
135
+ ensurePtyInterface
136
+ };
@@ -1,8 +1,31 @@
1
+ const crypto = require('crypto');
1
2
  const jwt = require('jsonwebtoken');
2
3
  const User = require('../models/User');
3
4
  const { getConfig } = require('../../config/manager');
4
5
 
5
- const JWT_SECRET = process.env.JWT_SECRET || 'nexuscli-secret-change-in-production';
6
+ let warnedEphemeralSecret = false;
7
+
8
+ function getJwtSecret() {
9
+ if (process.env.JWT_SECRET) {
10
+ return process.env.JWT_SECRET;
11
+ }
12
+
13
+ const config = getConfig();
14
+ if (config.auth?.jwt_secret) {
15
+ return config.auth.jwt_secret;
16
+ }
17
+
18
+ if (!global.__NEXUSCLI_EPHEMERAL_JWT_SECRET__) {
19
+ global.__NEXUSCLI_EPHEMERAL_JWT_SECRET__ = crypto.randomBytes(48).toString('hex');
20
+ }
21
+
22
+ if (!warnedEphemeralSecret) {
23
+ warnedEphemeralSecret = true;
24
+ console.warn('[Auth] JWT secret missing from env and config; using ephemeral in-memory secret for this process');
25
+ }
26
+
27
+ return global.__NEXUSCLI_EPHEMERAL_JWT_SECRET__;
28
+ }
6
29
 
7
30
  /**
8
31
  * Get config user (admin from init) by id
@@ -31,14 +54,14 @@ function generateToken(user) {
31
54
  username: user.username,
32
55
  role: user.role
33
56
  },
34
- JWT_SECRET,
57
+ getJwtSecret(),
35
58
  { expiresIn: JWT_EXPIRES_IN }
36
59
  );
37
60
  }
38
61
 
39
62
  function verifyToken(token) {
40
63
  try {
41
- return jwt.verify(token, JWT_SECRET);
64
+ return jwt.verify(token, getJwtSecret());
42
65
  } catch (err) {
43
66
  return null;
44
67
  }
@@ -99,5 +122,5 @@ module.exports = {
99
122
  verifyToken,
100
123
  authMiddleware,
101
124
  adminOnly,
102
- JWT_SECRET
125
+ getJwtSecret
103
126
  };
@@ -28,15 +28,19 @@ class Conversation {
28
28
 
29
29
  const sessionStmt = prepare(`
30
30
  INSERT INTO sessions (
31
- id, engine, workspace_path, title,
31
+ id, engine, lane, runtime_id, provider_id, model_id, workspace_path, title,
32
32
  last_used_at, created_at, message_count
33
33
  )
34
- VALUES (?, ?, ?, ?, ?, ?, ?)
34
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
35
35
  `);
36
36
 
37
37
  sessionStmt.run(
38
38
  id,
39
- 'claude-code',
39
+ 'claude',
40
+ 'native',
41
+ 'claude-native',
42
+ 'anthropic',
43
+ 'sonnet',
40
44
  workspace,
41
45
  title,
42
46
  now,
@@ -14,15 +14,31 @@ class Message {
14
14
  * @param {Object} metadata - Optional metadata
15
15
  * @param {number} createdAt - Optional timestamp override
16
16
  * @param {string} engine - Engine used ('claude' | 'codex' | 'gemini' | 'qwen')
17
+ * @param {Object} runtimeMeta - Optional runtime-aware metadata
17
18
  * @returns {Object} Created message
18
19
  */
19
- static create(conversationId, role, content, metadata = null, createdAt = Date.now(), engine = 'claude') {
20
+ static create(
21
+ conversationId,
22
+ role,
23
+ content,
24
+ metadata = null,
25
+ createdAt = Date.now(),
26
+ engine = 'claude',
27
+ runtimeMeta = {}
28
+ ) {
20
29
  const id = uuidv4();
21
30
  const now = createdAt || Date.now();
31
+ const lane = runtimeMeta.lane || 'native';
32
+ const runtimeId = runtimeMeta.runtimeId || `${engine}-native`;
33
+ const providerId = runtimeMeta.providerId || null;
34
+ const modelId = runtimeMeta.modelId || null;
22
35
 
23
36
  const stmt = prepare(`
24
- INSERT INTO messages (id, conversation_id, role, content, created_at, metadata, engine)
25
- VALUES (?, ?, ?, ?, ?, ?, ?)
37
+ INSERT INTO messages (
38
+ id, conversation_id, role, content, created_at, metadata, engine,
39
+ lane, runtime_id, provider_id, model_id
40
+ )
41
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
26
42
  `);
27
43
 
28
44
  stmt.run(
@@ -32,7 +48,11 @@ class Message {
32
48
  content,
33
49
  now,
34
50
  metadata ? JSON.stringify(metadata) : null,
35
- engine
51
+ engine,
52
+ lane,
53
+ runtimeId,
54
+ providerId,
55
+ modelId
36
56
  );
37
57
 
38
58
  // Touch conversation (update updated_at)
@@ -45,7 +65,11 @@ class Message {
45
65
  content,
46
66
  created_at: now,
47
67
  metadata,
48
- engine
68
+ engine,
69
+ lane,
70
+ runtime_id: runtimeId,
71
+ provider_id: providerId,
72
+ model_id: modelId
49
73
  };
50
74
  }
51
75