@mmmbuto/nexuscli 0.6.0 → 0.6.2
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.
|
@@ -71,27 +71,39 @@ router.post('/', async (req, res) => {
|
|
|
71
71
|
// Send initial event
|
|
72
72
|
res.write(`data: ${JSON.stringify({ type: 'message_start', messageId: `user-${Date.now()}`, sessionId })}\n\n`);
|
|
73
73
|
|
|
74
|
-
//
|
|
74
|
+
// Check if this is an engine switch (requires context bridging)
|
|
75
75
|
const lastEngine = Message.getLastEngine(sessionId);
|
|
76
|
-
const
|
|
77
|
-
sessionId,
|
|
78
|
-
fromEngine: lastEngine,
|
|
79
|
-
toEngine: 'claude',
|
|
80
|
-
userMessage: message
|
|
81
|
-
});
|
|
76
|
+
const isEngineBridge = lastEngine && lastEngine !== 'claude';
|
|
82
77
|
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
// IMPORTANT: Skip contextBridge for Claude native resume!
|
|
79
|
+
// When Claude uses `-r sessionId`, it loads full history from .jsonl file.
|
|
80
|
+
// ContextBridge is ONLY needed for:
|
|
81
|
+
// 1. Engine switches (e.g., Codex → Claude)
|
|
82
|
+
// 2. New sessions without history
|
|
83
|
+
let promptToSend = message;
|
|
85
84
|
|
|
86
|
-
console.log(`[Chat] Context: ${contextResult.contextTokens} tokens from ${contextResult.contextSource}, total: ${contextResult.totalTokens}`);
|
|
87
|
-
|
|
88
|
-
// Notify frontend about engine switch
|
|
89
85
|
if (isEngineBridge) {
|
|
86
|
+
// Engine switch: need context from previous engine
|
|
87
|
+
const contextResult = await contextBridge.buildContext({
|
|
88
|
+
sessionId,
|
|
89
|
+
fromEngine: lastEngine,
|
|
90
|
+
toEngine: 'claude',
|
|
91
|
+
userMessage: message
|
|
92
|
+
});
|
|
93
|
+
promptToSend = contextResult.prompt;
|
|
94
|
+
console.log(`[Chat] Engine bridge: ${contextResult.contextTokens} tokens from ${lastEngine}`);
|
|
95
|
+
|
|
90
96
|
res.write(`data: ${JSON.stringify({
|
|
91
97
|
type: 'status',
|
|
92
98
|
category: 'engine_switch',
|
|
93
99
|
message: `Continuing conversation from ${lastEngine}`
|
|
94
100
|
})}\n\n`);
|
|
101
|
+
} else if (!isNewSession) {
|
|
102
|
+
// Native resume: Claude will load history from session file
|
|
103
|
+
console.log(`[Chat] Native resume: Claude will use -r ${sessionId} (full history in .jsonl)`);
|
|
104
|
+
} else {
|
|
105
|
+
// New session: no context needed
|
|
106
|
+
console.log(`[Chat] New session: starting fresh`);
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
try {
|
|
@@ -113,7 +125,7 @@ router.post('/', async (req, res) => {
|
|
|
113
125
|
|
|
114
126
|
// Call Claude Code wrapper with workspace path for --cwd
|
|
115
127
|
const result = await claudeWrapper.sendMessage({
|
|
116
|
-
prompt:
|
|
128
|
+
prompt: promptToSend,
|
|
117
129
|
conversationId: sessionId,
|
|
118
130
|
model,
|
|
119
131
|
workspacePath, // Pass workspace for Claude CLI --cwd
|
|
@@ -82,28 +82,37 @@ router.post('/', async (req, res) => {
|
|
|
82
82
|
// Send initial event
|
|
83
83
|
res.write(`data: ${JSON.stringify({ type: 'message_start', messageId: `user-${Date.now()}`, sessionId })}\n\n`);
|
|
84
84
|
|
|
85
|
-
//
|
|
86
|
-
// Note: Codex uses codeOnly mode for better code-focused context
|
|
85
|
+
// Check if this is an engine switch (requires context bridging)
|
|
87
86
|
const lastEngine = Message.getLastEngine(sessionId);
|
|
88
|
-
const
|
|
89
|
-
sessionId,
|
|
90
|
-
fromEngine: lastEngine,
|
|
91
|
-
toEngine: 'codex',
|
|
92
|
-
userMessage: message
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const promptWithContext = contextResult.prompt;
|
|
96
|
-
const isEngineBridge = contextResult.isEngineBridge;
|
|
87
|
+
const isEngineBridge = lastEngine && lastEngine !== 'codex';
|
|
97
88
|
|
|
98
|
-
|
|
89
|
+
// IMPORTANT: Skip contextBridge for Codex native resume!
|
|
90
|
+
// When Codex uses `exec resume <threadId>`, it loads full history from native thread.
|
|
91
|
+
// ContextBridge is ONLY needed for engine switches.
|
|
92
|
+
let promptToSend = message;
|
|
99
93
|
|
|
100
|
-
// Notify frontend about engine switch
|
|
101
94
|
if (isEngineBridge) {
|
|
95
|
+
// Engine switch: need context from previous engine
|
|
96
|
+
const contextResult = await contextBridge.buildContext({
|
|
97
|
+
sessionId,
|
|
98
|
+
fromEngine: lastEngine,
|
|
99
|
+
toEngine: 'codex',
|
|
100
|
+
userMessage: message
|
|
101
|
+
});
|
|
102
|
+
promptToSend = contextResult.prompt;
|
|
103
|
+
console.log(`[Codex] Engine bridge: ${contextResult.contextTokens} tokens from ${lastEngine}`);
|
|
104
|
+
|
|
102
105
|
res.write(`data: ${JSON.stringify({
|
|
103
106
|
type: 'status',
|
|
104
107
|
category: 'engine_switch',
|
|
105
108
|
message: `Continuing conversation from ${lastEngine}`
|
|
106
109
|
})}\n\n`);
|
|
110
|
+
} else if (nativeThreadId) {
|
|
111
|
+
// Native resume: Codex will load history from thread
|
|
112
|
+
console.log(`[Codex] Native resume: Codex will use threadId ${nativeThreadId}`);
|
|
113
|
+
} else {
|
|
114
|
+
// New session: no context needed
|
|
115
|
+
console.log(`[Codex] New session: starting fresh`);
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
try {
|
|
@@ -124,7 +133,7 @@ router.post('/', async (req, res) => {
|
|
|
124
133
|
|
|
125
134
|
// Call Codex wrapper with native threadId for session resume
|
|
126
135
|
const result = await codexWrapper.sendMessage({
|
|
127
|
-
prompt:
|
|
136
|
+
prompt: promptToSend,
|
|
128
137
|
model,
|
|
129
138
|
threadId: nativeThreadId, // Native Codex thread ID for resume
|
|
130
139
|
reasoningEffort,
|
|
@@ -90,29 +90,38 @@ router.post('/', async (req, res) => {
|
|
|
90
90
|
engine: 'gemini'
|
|
91
91
|
})}\n\n`);
|
|
92
92
|
|
|
93
|
-
//
|
|
94
|
-
// Note: Gemini has larger context window, uses preferSummary: false config
|
|
93
|
+
// Check if this is an engine switch (requires context bridging)
|
|
95
94
|
const lastEngine = Message.getLastEngine(sessionId);
|
|
96
|
-
const
|
|
97
|
-
sessionId,
|
|
98
|
-
fromEngine: lastEngine,
|
|
99
|
-
toEngine: 'gemini',
|
|
100
|
-
userMessage: message
|
|
101
|
-
});
|
|
95
|
+
const isEngineBridge = lastEngine && lastEngine !== 'gemini';
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
// IMPORTANT: Skip contextBridge for Gemini native resume!
|
|
98
|
+
// When Gemini uses `--resume <sessionId>`, it loads full history from native session.
|
|
99
|
+
// ContextBridge is ONLY needed for engine switches.
|
|
100
|
+
let promptToSend = message;
|
|
105
101
|
|
|
106
|
-
console.log(`[Gemini] Context: ${contextResult.contextTokens} tokens from ${contextResult.contextSource}, total: ${contextResult.totalTokens}`);
|
|
107
|
-
|
|
108
|
-
// Notify frontend about engine switch
|
|
109
102
|
if (isEngineBridge) {
|
|
103
|
+
// Engine switch: need context from previous engine
|
|
104
|
+
const contextResult = await contextBridge.buildContext({
|
|
105
|
+
sessionId,
|
|
106
|
+
fromEngine: lastEngine,
|
|
107
|
+
toEngine: 'gemini',
|
|
108
|
+
userMessage: message
|
|
109
|
+
});
|
|
110
|
+
promptToSend = contextResult.prompt;
|
|
111
|
+
console.log(`[Gemini] Engine bridge: ${contextResult.contextTokens} tokens from ${lastEngine}`);
|
|
112
|
+
|
|
110
113
|
res.write(`data: ${JSON.stringify({
|
|
111
114
|
type: 'status',
|
|
112
115
|
category: 'system',
|
|
113
116
|
message: `Context bridged from ${lastEngine}`,
|
|
114
117
|
icon: '🔄'
|
|
115
118
|
})}\n\n`);
|
|
119
|
+
} else if (nativeSessionId) {
|
|
120
|
+
// Native resume: Gemini will load history from session
|
|
121
|
+
console.log(`[Gemini] Native resume: Gemini will use --resume ${nativeSessionId}`);
|
|
122
|
+
} else {
|
|
123
|
+
// New session: no context needed
|
|
124
|
+
console.log(`[Gemini] New session: starting fresh`);
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
// Save user message to DB
|
|
@@ -139,7 +148,7 @@ router.post('/', async (req, res) => {
|
|
|
139
148
|
|
|
140
149
|
// Call Gemini wrapper with native sessionId for session resume
|
|
141
150
|
const result = await geminiWrapper.sendMessage({
|
|
142
|
-
prompt:
|
|
151
|
+
prompt: promptToSend,
|
|
143
152
|
threadId: nativeSessionId, // Native Gemini session ID for resume
|
|
144
153
|
model,
|
|
145
154
|
workspacePath,
|
|
@@ -147,6 +147,15 @@ class SessionManager {
|
|
|
147
147
|
return crypto.createHash('md5').update(workspacePath).digest('hex').substring(0, 8);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Normalize workspace path (remove trailing slashes)
|
|
152
|
+
* Prevents duplicate entries like /path and /path/
|
|
153
|
+
*/
|
|
154
|
+
_normalizePath(workspacePath) {
|
|
155
|
+
if (!workspacePath) return '';
|
|
156
|
+
return workspacePath.replace(/\/+$/, '');
|
|
157
|
+
}
|
|
158
|
+
|
|
150
159
|
/**
|
|
151
160
|
* Get or create session for conversation + engine
|
|
152
161
|
*
|
|
@@ -164,16 +173,17 @@ class SessionManager {
|
|
|
164
173
|
*/
|
|
165
174
|
async getOrCreateSession(conversationId, engine, workspacePath) {
|
|
166
175
|
const normalizedEngine = this._normalizeEngine(engine);
|
|
176
|
+
const normalizedPath = this._normalizePath(workspacePath);
|
|
167
177
|
const cacheKey = this._getCacheKey(conversationId, normalizedEngine);
|
|
168
178
|
|
|
169
|
-
console.log(`[SessionManager] getOrCreateSession(${conversationId}, ${normalizedEngine}, ${
|
|
179
|
+
console.log(`[SessionManager] getOrCreateSession(${conversationId}, ${normalizedEngine}, ${normalizedPath})`);
|
|
170
180
|
|
|
171
181
|
// 1. Check RAM cache first (fastest path)
|
|
172
182
|
if (this.sessionMap.has(cacheKey)) {
|
|
173
183
|
const cachedId = this.sessionMap.get(cacheKey);
|
|
174
184
|
|
|
175
185
|
// Verify it still exists on filesystem
|
|
176
|
-
if (this.sessionFileExists(cachedId, normalizedEngine,
|
|
186
|
+
if (this.sessionFileExists(cachedId, normalizedEngine, normalizedPath)) {
|
|
177
187
|
this.lastAccess.set(cacheKey, Date.now());
|
|
178
188
|
console.log(`[SessionManager] Cache hit: ${cachedId}`);
|
|
179
189
|
return { sessionId: cachedId, isNew: false };
|
|
@@ -195,7 +205,7 @@ class SessionManager {
|
|
|
195
205
|
|
|
196
206
|
if (row) {
|
|
197
207
|
// 3. Verify session file exists on filesystem
|
|
198
|
-
if (this.sessionFileExists(row.id, normalizedEngine, row.workspace_path ||
|
|
208
|
+
if (this.sessionFileExists(row.id, normalizedEngine, row.workspace_path || normalizedPath)) {
|
|
199
209
|
// Valid session - update cache and return
|
|
200
210
|
this.sessionMap.set(cacheKey, row.id);
|
|
201
211
|
this.lastAccess.set(cacheKey, Date.now());
|
|
@@ -223,7 +233,7 @@ class SessionManager {
|
|
|
223
233
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
224
234
|
`);
|
|
225
235
|
const title = 'New Chat'; // Will be updated after first response
|
|
226
|
-
insertStmt.run(sessionId,
|
|
236
|
+
insertStmt.run(sessionId, normalizedPath, normalizedEngine, conversationId, title, now, now);
|
|
227
237
|
saveDb();
|
|
228
238
|
console.log(`[SessionManager] Saved to DB: ${sessionId}`);
|
|
229
239
|
} catch (dbErr) {
|