@mmmbuto/nexuscli 0.5.9 → 0.6.1
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,
|
|
@@ -58,6 +58,26 @@ class SessionManager {
|
|
|
58
58
|
return true; // Always trust DB for exec-mode CLI sessions
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// For Claude: search ALL project folders (session might be in different workspace)
|
|
62
|
+
if (normalizedEngine === 'claude') {
|
|
63
|
+
try {
|
|
64
|
+
const projectsDir = SESSION_DIRS.claude;
|
|
65
|
+
if (fs.existsSync(projectsDir)) {
|
|
66
|
+
const dirs = fs.readdirSync(projectsDir);
|
|
67
|
+
for (const dir of dirs) {
|
|
68
|
+
const sessionFile = path.join(projectsDir, dir, `${sessionId}.jsonl`);
|
|
69
|
+
if (fs.existsSync(sessionFile)) {
|
|
70
|
+
console.log(`[SessionManager] Claude session found: ${sessionFile}`);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.warn(`[SessionManager] Claude session lookup failed:`, err.message);
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
61
81
|
try {
|
|
62
82
|
const sessionPath = this.getSessionFilePath(sessionId, engine, workspacePath);
|
|
63
83
|
if (!sessionPath) return false;
|
|
@@ -165,12 +185,13 @@ class SessionManager {
|
|
|
165
185
|
}
|
|
166
186
|
|
|
167
187
|
// 2. Check DB for existing session mapping
|
|
188
|
+
// Note: Frontend may send sessionId as conversationId, so check both columns
|
|
168
189
|
try {
|
|
169
190
|
const stmt = prepare(`
|
|
170
191
|
SELECT id, workspace_path FROM sessions
|
|
171
|
-
WHERE conversation_id = ? AND engine = ?
|
|
192
|
+
WHERE (conversation_id = ? OR id = ?) AND engine = ?
|
|
172
193
|
`);
|
|
173
|
-
const row = stmt.get(conversationId, normalizedEngine);
|
|
194
|
+
const row = stmt.get(conversationId, conversationId, normalizedEngine);
|
|
174
195
|
|
|
175
196
|
if (row) {
|
|
176
197
|
// 3. Verify session file exists on filesystem
|