centaurus-cli 2.9.5 → 2.9.7
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-adapter.d.ts +27 -2
- package/dist/cli-adapter.d.ts.map +1 -1
- package/dist/cli-adapter.js +410 -17
- package/dist/cli-adapter.js.map +1 -1
- package/dist/config/slash-commands.d.ts.map +1 -1
- package/dist/config/slash-commands.js +7 -0
- package/dist/config/slash-commands.js.map +1 -1
- package/dist/context/context-manager.d.ts +4 -0
- package/dist/context/context-manager.d.ts.map +1 -1
- package/dist/context/context-manager.js +6 -0
- package/dist/context/context-manager.js.map +1 -1
- package/dist/context/handlers/docker-handler.d.ts +5 -1
- package/dist/context/handlers/docker-handler.d.ts.map +1 -1
- package/dist/context/handlers/docker-handler.js +27 -10
- package/dist/context/handlers/docker-handler.js.map +1 -1
- package/dist/context/handlers/ssh-handler.d.ts +47 -1
- package/dist/context/handlers/ssh-handler.d.ts.map +1 -1
- package/dist/context/handlers/ssh-handler.js +546 -73
- package/dist/context/handlers/ssh-handler.js.map +1 -1
- package/dist/context/handlers/wsl-handler.d.ts +5 -1
- package/dist/context/handlers/wsl-handler.d.ts.map +1 -1
- package/dist/context/handlers/wsl-handler.js +24 -6
- package/dist/context/handlers/wsl-handler.js.map +1 -1
- package/dist/context/subshell-handler.d.ts +8 -2
- package/dist/context/subshell-handler.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/services/checkpoint-manager.d.ts +162 -0
- package/dist/services/checkpoint-manager.d.ts.map +1 -0
- package/dist/services/checkpoint-manager.js +926 -0
- package/dist/services/checkpoint-manager.js.map +1 -0
- package/dist/services/fast-context-agent.d.ts +12 -0
- package/dist/services/fast-context-agent.d.ts.map +1 -0
- package/dist/services/fast-context-agent.js +253 -0
- package/dist/services/fast-context-agent.js.map +1 -0
- package/dist/tools/background-command.d.ts.map +1 -1
- package/dist/tools/background-command.js +132 -24
- package/dist/tools/background-command.js.map +1 -1
- package/dist/tools/command.d.ts.map +1 -1
- package/dist/tools/command.js +14 -4
- package/dist/tools/command.js.map +1 -1
- package/dist/tools/create-image.d.ts.map +1 -1
- package/dist/tools/create-image.js +43 -18
- package/dist/tools/create-image.js.map +1 -1
- package/dist/tools/fast-context.d.ts +3 -0
- package/dist/tools/fast-context.d.ts.map +1 -0
- package/dist/tools/fast-context.js +72 -0
- package/dist/tools/fast-context.js.map +1 -0
- package/dist/tools/file-ops.d.ts.map +1 -1
- package/dist/tools/file-ops.js +12 -12
- package/dist/tools/file-ops.js.map +1 -1
- package/dist/tools/find-files.d.ts +2 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +62 -2
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/get-diff.d.ts +9 -45
- package/dist/tools/get-diff.d.ts.map +1 -1
- package/dist/tools/get-diff.js +288 -171
- package/dist/tools/get-diff.js.map +1 -1
- package/dist/tools/types.d.ts +4 -1
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/ui/components/App.d.ts +8 -0
- package/dist/ui/components/App.d.ts.map +1 -1
- package/dist/ui/components/App.js +290 -85
- package/dist/ui/components/App.js.map +1 -1
- package/dist/ui/components/ConfirmPrompt.d.ts +2 -0
- package/dist/ui/components/ConfirmPrompt.d.ts.map +1 -1
- package/dist/ui/components/ConfirmPrompt.js +8 -3
- package/dist/ui/components/ConfirmPrompt.js.map +1 -1
- package/dist/ui/components/InputBox.d.ts +6 -0
- package/dist/ui/components/InputBox.d.ts.map +1 -1
- package/dist/ui/components/InputBox.js +130 -6
- package/dist/ui/components/InputBox.js.map +1 -1
- package/dist/ui/components/InteractiveShell.d.ts.map +1 -1
- package/dist/ui/components/InteractiveShell.js +50 -15
- package/dist/ui/components/InteractiveShell.js.map +1 -1
- package/dist/ui/components/MessageDisplay.d.ts.map +1 -1
- package/dist/ui/components/MessageDisplay.js +2 -2
- package/dist/ui/components/MessageDisplay.js.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.d.ts.map +1 -1
- package/dist/ui/components/ToolExecutionMessage.js +213 -18
- package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
- package/dist/utils/ansi-encoder.d.ts +5 -0
- package/dist/utils/ansi-encoder.d.ts.map +1 -1
- package/dist/utils/ansi-encoder.js +5 -5
- package/dist/utils/ansi-encoder.js.map +1 -1
- package/dist/utils/editor-utils.d.ts +5 -0
- package/dist/utils/editor-utils.d.ts.map +1 -1
- package/dist/utils/editor-utils.js +67 -0
- package/dist/utils/editor-utils.js.map +1 -1
- package/dist/utils/input-classifier.d.ts.map +1 -1
- package/dist/utils/input-classifier.js +2 -1
- package/dist/utils/input-classifier.js.map +1 -1
- package/dist/utils/terminal-output.d.ts.map +1 -1
- package/dist/utils/terminal-output.js +162 -103
- package/dist/utils/terminal-output.js.map +1 -1
- package/package.json +3 -1
package/dist/cli-adapter.js
CHANGED
|
@@ -22,6 +22,7 @@ import { backgroundCommandTool } from './tools/background-command.js';
|
|
|
22
22
|
import { subAgentTool } from './tools/sub-agent.js';
|
|
23
23
|
import { enterRemoteSessionTool } from './tools/enter-remote-session.js';
|
|
24
24
|
import { workflowTool } from './tools/workflow-tool.js';
|
|
25
|
+
import { fastContextTool } from './tools/fast-context.js';
|
|
25
26
|
import { SubAgentManager } from './services/sub-agent-manager.js';
|
|
26
27
|
import { ShellInputAgent } from './services/shell-input-agent.js';
|
|
27
28
|
import { apiClient } from './services/api-client.js';
|
|
@@ -46,6 +47,7 @@ import { BackgroundTaskManager } from './services/background-task-manager.js';
|
|
|
46
47
|
import { sessionQuotaManager } from './services/session-quota-manager.js';
|
|
47
48
|
import { ollamaService, OllamaService } from './services/ollama-service.js';
|
|
48
49
|
import { workflowStorage } from './services/workflow-storage.js';
|
|
50
|
+
import { CheckpointManager } from './services/checkpoint-manager.js';
|
|
49
51
|
export class CentaurusCLI {
|
|
50
52
|
configManager;
|
|
51
53
|
toolRegistry;
|
|
@@ -116,9 +118,16 @@ export class CentaurusCLI {
|
|
|
116
118
|
onPromptAnswered; // Callback when AI answers a shell prompt
|
|
117
119
|
onShowWorkflowCreatorCallback; // Callback to show workflow creator screen with optional initial steps
|
|
118
120
|
onWorkflowSaveCallback; // Callback when workflow is saved
|
|
121
|
+
onAiAutoSuggestChange; // Callback for AI auto-suggest setting changes
|
|
122
|
+
onRevertToCheckpointCallback; // Callback for revert UI update
|
|
123
|
+
// Checkpoint manager for revert functionality
|
|
124
|
+
checkpointManager;
|
|
125
|
+
currentCheckpointId; // Track current checkpoint being created
|
|
119
126
|
// Workflow learning mode state
|
|
120
127
|
workflowLearningActive = false;
|
|
121
128
|
learnedWorkflowSteps = [];
|
|
129
|
+
// Callback to set input value (e.g., for revert)
|
|
130
|
+
onSetInputCallback = null;
|
|
122
131
|
constructor() {
|
|
123
132
|
this.configManager = new ConfigManager();
|
|
124
133
|
this.toolRegistry = new ToolRegistry();
|
|
@@ -233,6 +242,24 @@ export class CentaurusCLI {
|
|
|
233
242
|
setOnWorkflowSave(callback) {
|
|
234
243
|
this.onWorkflowSaveCallback = callback;
|
|
235
244
|
}
|
|
245
|
+
setOnAiAutoSuggestChange(callback) {
|
|
246
|
+
this.onAiAutoSuggestChange = callback;
|
|
247
|
+
}
|
|
248
|
+
setOnRevertToCheckpoint(callback) {
|
|
249
|
+
this.onRevertToCheckpointCallback = callback;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get checkpoints for autocomplete dropdown
|
|
253
|
+
*/
|
|
254
|
+
getCheckpointsForAutocomplete() {
|
|
255
|
+
if (!this.checkpointManager)
|
|
256
|
+
return [];
|
|
257
|
+
return this.checkpointManager.list().map(cp => ({
|
|
258
|
+
id: cp.id,
|
|
259
|
+
prompt: cp.prompt,
|
|
260
|
+
timestamp: new Date(cp.createdAt)
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
236
263
|
/**
|
|
237
264
|
* Save a workflow from the workflow creator UI
|
|
238
265
|
*/
|
|
@@ -395,7 +422,10 @@ Begin executing now, starting with Step 1.`;
|
|
|
395
422
|
// starting 'fresh' in the remote user's home/default dir is safer for 'warpify' semantics regardless of type.
|
|
396
423
|
// However, to avoid regression for WSL/Docker usage where inheritance is expected, we restricts this fix to SSH.
|
|
397
424
|
const initialCwd = type === 'ssh' ? undefined : this.cwd;
|
|
398
|
-
|
|
425
|
+
// Use factory method to create a new handler instance for this session
|
|
426
|
+
// This prevents state issues where the singleton handler's client is overwritten by nested sessions
|
|
427
|
+
const newHandler = detection.handler.createNew();
|
|
428
|
+
context = await newHandler.connect(command, initialCwd);
|
|
399
429
|
}
|
|
400
430
|
this.connectionCommandStack.push(command);
|
|
401
431
|
this.contextManager.pushContext(context);
|
|
@@ -939,6 +969,7 @@ Begin executing now, starting with Step 1.`;
|
|
|
939
969
|
this.toolRegistry.register(subAgentTool);
|
|
940
970
|
this.toolRegistry.register(enterRemoteSessionTool);
|
|
941
971
|
this.toolRegistry.register(workflowTool);
|
|
972
|
+
this.toolRegistry.register(fastContextTool);
|
|
942
973
|
// Initialize SubAgentManager with tool registry
|
|
943
974
|
SubAgentManager.initialize(this.toolRegistry);
|
|
944
975
|
SubAgentManager.setOnSubAgentCountChange((count) => {
|
|
@@ -1279,12 +1310,104 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1279
1310
|
// Start logging session and log user message
|
|
1280
1311
|
conversationLogger.startSession();
|
|
1281
1312
|
conversationLogger.logUserMessage(message);
|
|
1313
|
+
// Initialize and start checkpoint for revert functionality
|
|
1314
|
+
if (!this.checkpointManager) {
|
|
1315
|
+
this.checkpointManager = new CheckpointManager();
|
|
1316
|
+
}
|
|
1317
|
+
// Ensure we have a chat ID for checkpoints (even for the first message)
|
|
1318
|
+
if (!this.currentChatId) {
|
|
1319
|
+
this.currentChatId = localChatStorage.generateChatId();
|
|
1320
|
+
}
|
|
1321
|
+
if (this.currentChatId) {
|
|
1322
|
+
this.checkpointManager.setCurrentChatId(this.currentChatId);
|
|
1323
|
+
this.currentCheckpointId = undefined;
|
|
1324
|
+
// Start checkpoint snapshot for this user message
|
|
1325
|
+
const currentContext = this.contextManager.getCurrentContext();
|
|
1326
|
+
// Build remote session info and handler for non-local contexts
|
|
1327
|
+
let remoteSessionInfo;
|
|
1328
|
+
let remoteHandler;
|
|
1329
|
+
if (currentContext.type !== 'local' && currentContext.handler) {
|
|
1330
|
+
const metadata = currentContext.metadata;
|
|
1331
|
+
remoteSessionInfo = {
|
|
1332
|
+
hostname: metadata.hostname,
|
|
1333
|
+
username: metadata.username,
|
|
1334
|
+
sessionId: currentContext.sessionId,
|
|
1335
|
+
connectionString: metadata.hostname
|
|
1336
|
+
? `${metadata.username || 'user'}@${metadata.hostname}`
|
|
1337
|
+
: metadata.distroName || metadata.containerId || undefined,
|
|
1338
|
+
};
|
|
1339
|
+
// Use the handler if it implements the RemoteFileHandler interface
|
|
1340
|
+
if (typeof currentContext.handler.readFile === 'function' &&
|
|
1341
|
+
typeof currentContext.handler.writeFile === 'function' &&
|
|
1342
|
+
typeof currentContext.handler.executeCommand === 'function' &&
|
|
1343
|
+
typeof currentContext.handler.isConnected === 'function') {
|
|
1344
|
+
remoteHandler = currentContext.handler;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
try {
|
|
1348
|
+
const checkpointStartPromise = this.checkpointManager.startCheckpoint({
|
|
1349
|
+
prompt: message,
|
|
1350
|
+
cwd: this.cwd,
|
|
1351
|
+
contextType: currentContext.type,
|
|
1352
|
+
conversationIndex: this.conversationHistory.length - 1,
|
|
1353
|
+
uiMessageIndex: this.uiMessageHistory.length,
|
|
1354
|
+
remoteSessionInfo,
|
|
1355
|
+
handler: remoteHandler,
|
|
1356
|
+
});
|
|
1357
|
+
let checkpoint = null;
|
|
1358
|
+
if (currentContext.type !== 'local') {
|
|
1359
|
+
const remoteCheckpointTimeoutMs = 2000;
|
|
1360
|
+
const timeoutMarker = '__checkpoint_timeout__';
|
|
1361
|
+
let timeoutHandle = null;
|
|
1362
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
1363
|
+
timeoutHandle = setTimeout(() => resolve(timeoutMarker), remoteCheckpointTimeoutMs);
|
|
1364
|
+
});
|
|
1365
|
+
const checkpointOrTimeout = await Promise.race([
|
|
1366
|
+
checkpointStartPromise,
|
|
1367
|
+
timeoutPromise,
|
|
1368
|
+
]);
|
|
1369
|
+
if (timeoutHandle) {
|
|
1370
|
+
clearTimeout(timeoutHandle);
|
|
1371
|
+
}
|
|
1372
|
+
if (checkpointOrTimeout === timeoutMarker) {
|
|
1373
|
+
quickLog(`[${new Date().toISOString()}] [Checkpoint] Remote checkpoint start exceeded ${remoteCheckpointTimeoutMs}ms (${currentContext.type}); continuing without blocking AI turn\n`);
|
|
1374
|
+
void checkpointStartPromise
|
|
1375
|
+
.then((lateCheckpoint) => {
|
|
1376
|
+
if (!lateCheckpoint || !this.checkpointManager) {
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
this.checkpointManager.discardCheckpointById(lateCheckpoint.id);
|
|
1380
|
+
quickLog(`[${new Date().toISOString()}] [Checkpoint] Discarded late checkpoint ${lateCheckpoint.id} created after timeout\n`);
|
|
1381
|
+
})
|
|
1382
|
+
.catch((lateError) => {
|
|
1383
|
+
quickLog(`[${new Date().toISOString()}] [Checkpoint] Late checkpoint creation failed after timeout: ${lateError?.message || lateError}\n`);
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
else {
|
|
1387
|
+
checkpoint = checkpointOrTimeout;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
else {
|
|
1391
|
+
checkpoint = await checkpointStartPromise;
|
|
1392
|
+
}
|
|
1393
|
+
if (checkpoint) {
|
|
1394
|
+
this.currentCheckpointId = checkpoint.id;
|
|
1395
|
+
quickLog(`[${new Date().toISOString()}] [Checkpoint] Started checkpoint ${checkpoint.id} (${currentContext.type}) for: "${message.slice(0, 50)}..."\n`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
catch (error) {
|
|
1399
|
+
quickLog(`[${new Date().toISOString()}] [Checkpoint] Failed to start checkpoint: ${error.message}\n`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1282
1402
|
try {
|
|
1283
1403
|
const tools = this.toolRegistry.getSchemas();
|
|
1284
1404
|
const context = {
|
|
1285
1405
|
cwd: this.cwd,
|
|
1286
1406
|
contextManager: this.contextManager,
|
|
1287
1407
|
cliAdapter: this, // Pass CLI adapter reference for interactive process management
|
|
1408
|
+
checkpointManager: this.checkpointManager, // For session-aware diff tool
|
|
1409
|
+
currentCheckpointId: this.currentCheckpointId, // Active checkpoint for this request
|
|
1410
|
+
currentChatId: this.currentChatId || undefined, // Current chat session ID (for session-wide diffs)
|
|
1288
1411
|
requireApproval: async (message, risky, preview, operationType, operationDetails) => {
|
|
1289
1412
|
// Special bypass for shell input to running processes:
|
|
1290
1413
|
// If the AI is sending input to an existing shell (via shell_input), we bypass the separate approval step.
|
|
@@ -1297,10 +1420,10 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
1297
1420
|
}
|
|
1298
1421
|
return true; // Auto-approve if no callback registered
|
|
1299
1422
|
},
|
|
1300
|
-
onStreamingOutput: (chunk, type) => {
|
|
1423
|
+
onStreamingOutput: (chunk, type, toolName) => {
|
|
1301
1424
|
// Forward streaming output to UI callback
|
|
1302
1425
|
if (this.onToolStreamingOutput) {
|
|
1303
|
-
this.onToolStreamingOutput({ toolName: 'execute_command', chunk, type });
|
|
1426
|
+
this.onToolStreamingOutput({ toolName: toolName || 'execute_command', chunk, type });
|
|
1304
1427
|
}
|
|
1305
1428
|
},
|
|
1306
1429
|
};
|
|
@@ -2391,6 +2514,17 @@ DO NOT use write_to_file, edit_file, or execute_command until the plan is approv
|
|
|
2391
2514
|
throw new Error(`AI Error: ${error.message}`);
|
|
2392
2515
|
}
|
|
2393
2516
|
finally {
|
|
2517
|
+
// Finalize checkpoint if one was created for this message
|
|
2518
|
+
if (this.currentCheckpointId && this.checkpointManager) {
|
|
2519
|
+
try {
|
|
2520
|
+
await this.checkpointManager.finalizeCheckpoint(this.currentCheckpointId);
|
|
2521
|
+
quickLog(`[${new Date().toISOString()}] [Checkpoint] Finalized checkpoint ${this.currentCheckpointId}\n`);
|
|
2522
|
+
}
|
|
2523
|
+
catch (error) {
|
|
2524
|
+
quickLog(`[${new Date().toISOString()}] [Checkpoint] Failed to finalize checkpoint: ${error.message}\n`);
|
|
2525
|
+
}
|
|
2526
|
+
this.currentCheckpointId = undefined;
|
|
2527
|
+
}
|
|
2394
2528
|
// Clean up abort controller
|
|
2395
2529
|
this.currentAbortController = undefined;
|
|
2396
2530
|
}
|
|
@@ -2813,12 +2947,18 @@ Start by listing the directory structure to understand what you're working with.
|
|
|
2813
2947
|
const value = args[1].toLowerCase();
|
|
2814
2948
|
if (value === 'on') {
|
|
2815
2949
|
this.configManager.set('aiAutoSuggest', true);
|
|
2950
|
+
if (this.onAiAutoSuggestChange) {
|
|
2951
|
+
this.onAiAutoSuggestChange(true);
|
|
2952
|
+
}
|
|
2816
2953
|
responseMessage = '✅ **AI Auto-Suggestions Enabled**\n\n' +
|
|
2817
2954
|
'From now on, I will suggest commands after 5 seconds of inactivity.\n' +
|
|
2818
2955
|
'Suggestions will appear in grey text. Use the **Right Arrow** key to accept them.';
|
|
2819
2956
|
}
|
|
2820
2957
|
else if (value === 'off') {
|
|
2821
2958
|
this.configManager.set('aiAutoSuggest', false);
|
|
2959
|
+
if (this.onAiAutoSuggestChange) {
|
|
2960
|
+
this.onAiAutoSuggestChange(false);
|
|
2961
|
+
}
|
|
2822
2962
|
responseMessage = '✅ **AI Auto-Suggestions Disabled**\n\n' +
|
|
2823
2963
|
'I will no longer provide AI-powered command suggestions.';
|
|
2824
2964
|
}
|
|
@@ -3075,6 +3215,189 @@ Then try /models local again.`;
|
|
|
3075
3215
|
case 'exit':
|
|
3076
3216
|
process.exit(0);
|
|
3077
3217
|
break;
|
|
3218
|
+
case 'revert': {
|
|
3219
|
+
const checkpointArg = args[0];
|
|
3220
|
+
if (!checkpointArg) {
|
|
3221
|
+
responseMessage = '❌ Usage: `/revert <checkpoint-id>`\nType `/revert ` and use autocomplete to select a checkpoint.';
|
|
3222
|
+
break;
|
|
3223
|
+
}
|
|
3224
|
+
if (!this.checkpointManager) {
|
|
3225
|
+
responseMessage = '❌ No checkpoints available in this session.';
|
|
3226
|
+
break;
|
|
3227
|
+
}
|
|
3228
|
+
try {
|
|
3229
|
+
// Smart context matching: determine if we can revert based on current vs checkpoint context
|
|
3230
|
+
const currentContext = this.contextManager.getCurrentContext();
|
|
3231
|
+
const allCheckpoints = this.checkpointManager.list();
|
|
3232
|
+
const targetCheckpoint = allCheckpoints.find(c => c.id === checkpointArg);
|
|
3233
|
+
if (!targetCheckpoint) {
|
|
3234
|
+
responseMessage = `❌ Checkpoint "${checkpointArg}" not found.`;
|
|
3235
|
+
break;
|
|
3236
|
+
}
|
|
3237
|
+
// Context validation for revert
|
|
3238
|
+
const cpContextType = targetCheckpoint.contextType;
|
|
3239
|
+
const currentContextType = currentContext.type;
|
|
3240
|
+
if (cpContextType !== 'local' && currentContextType === 'local') {
|
|
3241
|
+
// Remote checkpoint but we're local — can't reconnect to SSH to revert files
|
|
3242
|
+
const sessionType = cpContextType.toUpperCase();
|
|
3243
|
+
const sessionInfo = targetCheckpoint.remoteSessionInfo;
|
|
3244
|
+
const target = sessionInfo?.connectionString || sessionInfo?.hostname || 'the remote machine';
|
|
3245
|
+
responseMessage = `❌ This checkpoint was created during a ${sessionType} session (${target}).\n` +
|
|
3246
|
+
`You are not currently connected to that session.\n\n` +
|
|
3247
|
+
`Please reconnect to the ${sessionType} session first, then retry /revert.`;
|
|
3248
|
+
break;
|
|
3249
|
+
}
|
|
3250
|
+
if (cpContextType === 'local' && currentContextType !== 'local') {
|
|
3251
|
+
// Local checkpoint but we're in a remote session — wrong context
|
|
3252
|
+
responseMessage = `❌ This checkpoint was created in a local session.\n` +
|
|
3253
|
+
`You are currently in a ${currentContextType.toUpperCase()} session.\n\n` +
|
|
3254
|
+
`Please exit your remote session first, then retry /revert.`;
|
|
3255
|
+
break;
|
|
3256
|
+
}
|
|
3257
|
+
if (cpContextType !== 'local' && currentContextType !== 'local' && cpContextType !== currentContextType) {
|
|
3258
|
+
// Both remote but different types (e.g., SSH checkpoint but in Docker now)
|
|
3259
|
+
responseMessage = `❌ This checkpoint was created in a ${cpContextType.toUpperCase()} session, ` +
|
|
3260
|
+
`but you are currently in a ${currentContextType.toUpperCase()} session.\n\n` +
|
|
3261
|
+
`Please connect to the correct ${cpContextType.toUpperCase()} session first.`;
|
|
3262
|
+
break;
|
|
3263
|
+
}
|
|
3264
|
+
// For remote checkpoints with mismatched hosts, validate the connection target
|
|
3265
|
+
if (cpContextType !== 'local' && targetCheckpoint.remoteSessionInfo) {
|
|
3266
|
+
const cpHost = targetCheckpoint.remoteSessionInfo.hostname;
|
|
3267
|
+
const currentHost = currentContext.metadata.hostname;
|
|
3268
|
+
if (cpHost && currentHost && cpHost !== currentHost) {
|
|
3269
|
+
responseMessage = `❌ This checkpoint was created on ${cpHost}, ` +
|
|
3270
|
+
`but you are currently connected to ${currentHost}.\n\n` +
|
|
3271
|
+
`Please connect to ${cpHost} first, then retry /revert.`;
|
|
3272
|
+
break;
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
// Build handler for remote revert if needed
|
|
3276
|
+
let revertHandler;
|
|
3277
|
+
if (cpContextType !== 'local' && currentContext.handler) {
|
|
3278
|
+
if (typeof currentContext.handler.readFile === 'function' &&
|
|
3279
|
+
typeof currentContext.handler.writeFile === 'function' &&
|
|
3280
|
+
typeof currentContext.handler.executeCommand === 'function' &&
|
|
3281
|
+
typeof currentContext.handler.isConnected === 'function') {
|
|
3282
|
+
revertHandler = currentContext.handler;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
const result = await this.checkpointManager.revertToCheckpoint(checkpointArg, revertHandler);
|
|
3286
|
+
// Find checkpoint index for UI truncation
|
|
3287
|
+
const checkpoints = this.checkpointManager.list();
|
|
3288
|
+
const checkpointIndex = checkpoints.findIndex(c => c.id === checkpointArg);
|
|
3289
|
+
// Truncate conversation history to the checkpoint
|
|
3290
|
+
if (checkpointIndex >= 0) {
|
|
3291
|
+
const checkpoint = result.checkpoint;
|
|
3292
|
+
// Populate the input bar with the reverted prompt
|
|
3293
|
+
if (this.onSetInputCallback) {
|
|
3294
|
+
this.onSetInputCallback(checkpoint.prompt);
|
|
3295
|
+
}
|
|
3296
|
+
// Use conversationIndex from metadata if available (robust)
|
|
3297
|
+
// This avoids issues with duplicate prompts getting truncated at the wrong occurrence
|
|
3298
|
+
if (typeof checkpoint.conversationIndex === 'number' &&
|
|
3299
|
+
checkpoint.conversationIndex >= 0 &&
|
|
3300
|
+
checkpoint.conversationIndex < this.conversationHistory.length) {
|
|
3301
|
+
// Truncate to index (exclusive) to remove the message from history
|
|
3302
|
+
// This allows the user to "edit" the message in the input bar
|
|
3303
|
+
this.conversationHistory = this.conversationHistory.slice(0, checkpoint.conversationIndex);
|
|
3304
|
+
quickLog(`[${new Date().toISOString()}] [Revert] Truncated conversation history to index ${checkpoint.conversationIndex} (exclusive)\n`);
|
|
3305
|
+
}
|
|
3306
|
+
else {
|
|
3307
|
+
// Fallback to string matching (legacy or missing index)
|
|
3308
|
+
const targetPrompt = checkpoint.prompt;
|
|
3309
|
+
// Find the user message with this prompt in conversationHistory
|
|
3310
|
+
let foundIndex = -1;
|
|
3311
|
+
// Use a broader search to find the matching message
|
|
3312
|
+
// The prompt might be a substring or exact match
|
|
3313
|
+
const searchPrompt = targetPrompt.slice(0, 50);
|
|
3314
|
+
for (let i = 0; i < this.conversationHistory.length; i++) {
|
|
3315
|
+
const msg = this.conversationHistory[i];
|
|
3316
|
+
if (msg.role === 'user' && msg.content && msg.content.includes(searchPrompt)) {
|
|
3317
|
+
foundIndex = i;
|
|
3318
|
+
break;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
if (foundIndex >= 0) {
|
|
3322
|
+
// Keep messages up to but NOT including this user message
|
|
3323
|
+
this.conversationHistory = this.conversationHistory.slice(0, foundIndex);
|
|
3324
|
+
quickLog(`[${new Date().toISOString()}] [Revert] Truncated conversation history to index ${foundIndex} (exclusive, using string match)\n`);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
// Fix: Also truncate UI message history to ensure consistency
|
|
3328
|
+
// Use uiMessageIndex from metadata if available (robust)
|
|
3329
|
+
if (typeof checkpoint.uiMessageIndex === 'number' &&
|
|
3330
|
+
checkpoint.uiMessageIndex >= 0 &&
|
|
3331
|
+
checkpoint.uiMessageIndex < this.uiMessageHistory.length) {
|
|
3332
|
+
// Truncate to index (exclusive)
|
|
3333
|
+
this.uiMessageHistory = this.uiMessageHistory.slice(0, checkpoint.uiMessageIndex);
|
|
3334
|
+
quickLog(`[${new Date().toISOString()}] [Revert] Truncated UI message history to index ${checkpoint.uiMessageIndex} (exclusive)\n`);
|
|
3335
|
+
}
|
|
3336
|
+
else {
|
|
3337
|
+
// Fallback to string matching (legacy or missing index)
|
|
3338
|
+
const targetPrompt = checkpoint.prompt;
|
|
3339
|
+
const searchPrompt = targetPrompt.slice(0, 50);
|
|
3340
|
+
let foundUiIndex = -1;
|
|
3341
|
+
for (let i = 0; i < this.uiMessageHistory.length; i++) {
|
|
3342
|
+
const msg = this.uiMessageHistory[i];
|
|
3343
|
+
if (msg.role === 'user' && msg.content && msg.content.includes(searchPrompt)) {
|
|
3344
|
+
foundUiIndex = i;
|
|
3345
|
+
break;
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
if (foundUiIndex >= 0) {
|
|
3349
|
+
// Keep messages up to but NOT including this user message
|
|
3350
|
+
this.uiMessageHistory = this.uiMessageHistory.slice(0, foundUiIndex);
|
|
3351
|
+
quickLog(`[${new Date().toISOString()}] [Revert] Truncated UI message history to index ${foundUiIndex} (exclusive, using string match)\n`);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
// Remove the reverted checkpoint AND all checkpoints created after it
|
|
3355
|
+
// They should not appear in autocomplete anymore
|
|
3356
|
+
this.checkpointManager.removeCheckpointsFrom(checkpointArg);
|
|
3357
|
+
// Build the success message BEFORE calling handleChatPickerSelection
|
|
3358
|
+
// This will be passed as a parameter and included in the same React setState call
|
|
3359
|
+
quickLog(`[${new Date().toISOString()}] [Revert] Building success message for checkpoint "${checkpointArg}"\n`);
|
|
3360
|
+
const truncatedPrompt = result.checkpoint.prompt.length > 50
|
|
3361
|
+
? result.checkpoint.prompt.slice(0, 50) + '...'
|
|
3362
|
+
: result.checkpoint.prompt;
|
|
3363
|
+
let revertSuccessMessage = `✅ Reverted to: "${truncatedPrompt}"\n` +
|
|
3364
|
+
`Restored ${result.restored} files, removed ${result.removed} files.`;
|
|
3365
|
+
if (result.errors.length > 0) {
|
|
3366
|
+
revertSuccessMessage += `\n⚠️ Warnings: ${result.errors.join(', ')}`;
|
|
3367
|
+
}
|
|
3368
|
+
// Save the truncated state to disk
|
|
3369
|
+
if (this.currentChatId) {
|
|
3370
|
+
quickLog(`[${new Date().toISOString()}] [Revert] Saving truncated chat to disk (chatId: ${this.currentChatId})\n`);
|
|
3371
|
+
quickLog(`[${new Date().toISOString()}] [Revert] uiMessageHistory.length BEFORE save: ${this.uiMessageHistory.length}\n`);
|
|
3372
|
+
this.saveCurrentChat();
|
|
3373
|
+
// Reload the chat to force UI update ensures consistency
|
|
3374
|
+
// This simulates a /chat resume command which correctly populates the UI
|
|
3375
|
+
// Pass skipLoadedMessage=true and the revert success message
|
|
3376
|
+
// The success message is appended to restoredMessages in handleChatPickerSelection
|
|
3377
|
+
// This ensures it's part of the same React setState call, avoiding race conditions
|
|
3378
|
+
quickLog(`[${new Date().toISOString()}] [Revert] Calling handleChatPickerSelection with revertSuccessMessage="${revertSuccessMessage.substring(0, 50)}..."\n`);
|
|
3379
|
+
const currentContext = this.contextManager.getCurrentContext();
|
|
3380
|
+
const preserveRemote = currentContext.type !== 'local' && typeof currentContext.handler?.isConnected === 'function'
|
|
3381
|
+
? currentContext.handler.isConnected()
|
|
3382
|
+
: currentContext.type !== 'local';
|
|
3383
|
+
await this.handleChatPickerSelection(this.currentChatId, true, revertSuccessMessage, preserveRemote);
|
|
3384
|
+
// Clear responseMessage so it doesn't get sent via onDirectMessageCallback
|
|
3385
|
+
// (the message was already included in the restored messages above)
|
|
3386
|
+
quickLog(`[${new Date().toISOString()}] [Revert] handleChatPickerSelection completed, clearing responseMessage\n`);
|
|
3387
|
+
responseMessage = '';
|
|
3388
|
+
}
|
|
3389
|
+
else {
|
|
3390
|
+
// Fallback: if no current chat, show the message via normal callback
|
|
3391
|
+
quickLog(`[${new Date().toISOString()}] [Revert] No currentChatId, using fallback responseMessage\n`);
|
|
3392
|
+
responseMessage = revertSuccessMessage;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
catch (error) {
|
|
3397
|
+
responseMessage = `❌ Failed to revert: ${error.message}`;
|
|
3398
|
+
}
|
|
3399
|
+
break;
|
|
3400
|
+
}
|
|
3078
3401
|
case 'add-command':
|
|
3079
3402
|
case 'add-command-auto-detect':
|
|
3080
3403
|
// Handle custom command auto-detect management
|
|
@@ -3945,7 +4268,7 @@ Create once, run many times across different machines.`;
|
|
|
3945
4268
|
/**
|
|
3946
4269
|
* Load a chat from local storage and restore it
|
|
3947
4270
|
*/
|
|
3948
|
-
loadChat(chatId) {
|
|
4271
|
+
loadChat(chatId, options) {
|
|
3949
4272
|
const chat = localChatStorage.loadChat(chatId);
|
|
3950
4273
|
if (!chat) {
|
|
3951
4274
|
return false;
|
|
@@ -3957,11 +4280,32 @@ Create once, run many times across different machines.`;
|
|
|
3957
4280
|
tool_calls: msg.tool_calls,
|
|
3958
4281
|
tool_call_id: msg.tool_call_id,
|
|
3959
4282
|
}));
|
|
4283
|
+
// IMPORTANT: Also restore UI message history from saved chat
|
|
4284
|
+
// This ensures that when the chat is saved again (e.g., after revert),
|
|
4285
|
+
// the correct UI messages are preserved
|
|
4286
|
+
if (chat.uiMessages && chat.uiMessages.length > 0) {
|
|
4287
|
+
this.uiMessageHistory = chat.uiMessages.map(msg => ({
|
|
4288
|
+
id: msg.id,
|
|
4289
|
+
role: msg.role,
|
|
4290
|
+
content: msg.content,
|
|
4291
|
+
timestamp: msg.timestamp ? new Date(msg.timestamp) : undefined,
|
|
4292
|
+
toolExecution: msg.toolExecution,
|
|
4293
|
+
shouldStream: false,
|
|
4294
|
+
isCommandMode: msg.isCommandMode,
|
|
4295
|
+
tool_call_id: msg.tool_call_id,
|
|
4296
|
+
tool_calls: msg.tool_calls,
|
|
4297
|
+
thinkingDuration: msg.thinkingDuration,
|
|
4298
|
+
taskCompletion: msg.taskCompletion,
|
|
4299
|
+
planAccepted: msg.planAccepted,
|
|
4300
|
+
connectionStatus: msg.connectionStatus,
|
|
4301
|
+
}));
|
|
4302
|
+
}
|
|
3960
4303
|
// Set the current chat ID to continue the conversation
|
|
3961
4304
|
this.currentChatId = chatId;
|
|
3962
4305
|
this.conversationStarted = true;
|
|
3963
4306
|
// Restore CWD if saved (important for commands to run in correct directory)
|
|
3964
|
-
|
|
4307
|
+
// Allow skipping when preserving an active remote session
|
|
4308
|
+
if (chat.cwd && !options?.preserveCwd) {
|
|
3965
4309
|
this.cwd = chat.cwd;
|
|
3966
4310
|
// Update context manager if available
|
|
3967
4311
|
if (this.contextManager) {
|
|
@@ -3993,8 +4337,12 @@ Create once, run many times across different machines.`;
|
|
|
3993
4337
|
}
|
|
3994
4338
|
/**
|
|
3995
4339
|
* Handle chat picker selection
|
|
4340
|
+
* @param chatId The ID of the chat to load
|
|
4341
|
+
* @param skipLoadedMessage If true, skip showing the "Loaded chat" message (used during revert to avoid React setState race condition)
|
|
4342
|
+
* @param revertSuccessMessage If provided, this message will be appended to restoredMessages before calling onRestoreMessagesCallback (used during revert to include success message in the same React setState call, avoiding race conditions)
|
|
4343
|
+
* @param preserveRemoteSession If true, keep current SSH/WSL/Docker connection and skip reconnect logic (used during revert in a live remote session)
|
|
3996
4344
|
*/
|
|
3997
|
-
async handleChatPickerSelection(chatId) {
|
|
4345
|
+
async handleChatPickerSelection(chatId, skipLoadedMessage = false, revertSuccessMessage, preserveRemoteSession = false) {
|
|
3998
4346
|
const chat = localChatStorage.loadChat(chatId);
|
|
3999
4347
|
if (!chat) {
|
|
4000
4348
|
if (this.onResponseCallback) {
|
|
@@ -4005,7 +4353,8 @@ Create once, run many times across different machines.`;
|
|
|
4005
4353
|
// IMPORTANT: Clean up current remote session before loading new chat
|
|
4006
4354
|
// This ensures that switching from a remote chat to a local chat properly resets the state
|
|
4007
4355
|
const currentContext = this.contextManager.getCurrentContext();
|
|
4008
|
-
|
|
4356
|
+
const shouldPreserveRemote = preserveRemoteSession && currentContext.type !== 'local';
|
|
4357
|
+
if (!shouldPreserveRemote && currentContext.type !== 'local') {
|
|
4009
4358
|
// Disconnect from current remote session
|
|
4010
4359
|
if (currentContext.handler) {
|
|
4011
4360
|
try {
|
|
@@ -4022,8 +4371,18 @@ Create once, run many times across different machines.`;
|
|
|
4022
4371
|
this.connectionCommandStack = [];
|
|
4023
4372
|
}
|
|
4024
4373
|
// Load AI context
|
|
4025
|
-
this.loadChat(chatId);
|
|
4374
|
+
this.loadChat(chatId, shouldPreserveRemote ? { preserveCwd: true } : undefined);
|
|
4375
|
+
// If we're preserving a live remote session (revert case), restore the current remote CWD
|
|
4376
|
+
if (shouldPreserveRemote) {
|
|
4377
|
+
this.cwd = currentContext.metadata.workingDirectory;
|
|
4378
|
+
this.contextManager.updateWorkingDirectory(this.cwd);
|
|
4379
|
+
if (this.onCwdChange) {
|
|
4380
|
+
this.onCwdChange(this.cwd);
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4026
4383
|
// Restore UI messages if available
|
|
4384
|
+
quickLog(`[${new Date().toISOString()}] [handleChatPickerSelection] chat.uiMessages exists: ${!!chat.uiMessages}, length: ${chat.uiMessages?.length ?? 0}\n`);
|
|
4385
|
+
quickLog(`[${new Date().toISOString()}] [handleChatPickerSelection] onRestoreMessagesCallback exists: ${!!this.onRestoreMessagesCallback}\n`);
|
|
4027
4386
|
if (chat.uiMessages && chat.uiMessages.length > 0 && this.onRestoreMessagesCallback) {
|
|
4028
4387
|
// Convert StoredUIMessage back to Message format
|
|
4029
4388
|
const restoredMessages = chat.uiMessages.map(msg => ({
|
|
@@ -4041,11 +4400,29 @@ Create once, run many times across different machines.`;
|
|
|
4041
4400
|
planAccepted: msg.planAccepted,
|
|
4042
4401
|
connectionStatus: msg.connectionStatus, // For SSH/WSL/Docker connection status boxes
|
|
4043
4402
|
}));
|
|
4403
|
+
// If a revert success message was provided, append it to the restored messages
|
|
4404
|
+
// This ensures the message is part of the same React setState call, avoiding race conditions
|
|
4405
|
+
quickLog(`[${new Date().toISOString()}] [handleChatPickerSelection] revertSuccessMessage provided: ${!!revertSuccessMessage}\n`);
|
|
4406
|
+
if (revertSuccessMessage) {
|
|
4407
|
+
const revertSystemMessage = {
|
|
4408
|
+
id: `revert-success-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
4409
|
+
role: 'assistant',
|
|
4410
|
+
content: revertSuccessMessage,
|
|
4411
|
+
timestamp: new Date(),
|
|
4412
|
+
shouldStream: false,
|
|
4413
|
+
};
|
|
4414
|
+
restoredMessages.push(revertSystemMessage);
|
|
4415
|
+
quickLog(`[${new Date().toISOString()}] [handleChatPickerSelection] Appended revert success message, restoredMessages.length: ${restoredMessages.length}\n`);
|
|
4416
|
+
}
|
|
4417
|
+
quickLog(`[${new Date().toISOString()}] [handleChatPickerSelection] Calling onRestoreMessagesCallback with ${restoredMessages.length} messages\n`);
|
|
4044
4418
|
this.onRestoreMessagesCallback(restoredMessages);
|
|
4419
|
+
quickLog(`[${new Date().toISOString()}] [handleChatPickerSelection] onRestoreMessagesCallback completed\n`);
|
|
4045
4420
|
}
|
|
4046
4421
|
// Attempt to restore remote context if chat was saved while in remote environment
|
|
4047
4422
|
// Support both remoteContextStack (for nested sessions) and single remoteContext (backward compat)
|
|
4048
|
-
const contextStackToRestore =
|
|
4423
|
+
const contextStackToRestore = shouldPreserveRemote
|
|
4424
|
+
? null
|
|
4425
|
+
: (chat.remoteContextStack ?? (chat.remoteContext ? [chat.remoteContext] : null));
|
|
4049
4426
|
if (contextStackToRestore && contextStackToRestore.length > 0) {
|
|
4050
4427
|
// Get the base local CWD from the first context's localCwdBeforeRemote
|
|
4051
4428
|
const baseLocalCwd = contextStackToRestore[0].localCwdBeforeRemote;
|
|
@@ -4082,12 +4459,15 @@ Create once, run many times across different machines.`;
|
|
|
4082
4459
|
if (!detection) {
|
|
4083
4460
|
throw new Error(`Could not detect handler for: ${connectionCommand}`);
|
|
4084
4461
|
}
|
|
4462
|
+
// Create a new instance of the handler to ensure each level of the connection
|
|
4463
|
+
// has its own state (client, stream, etc.), critical for nested sessions of the same type
|
|
4464
|
+
const handler = detection.handler.createNew();
|
|
4085
4465
|
let context;
|
|
4086
4466
|
// Check if this is a nested connection (i > 0 means we're inside a remote session)
|
|
4087
4467
|
if (i > 0) {
|
|
4088
4468
|
const currentContext = this.contextManager.getCurrentContext();
|
|
4089
|
-
if (
|
|
4090
|
-
context = await
|
|
4469
|
+
if (handler.connectFromRemote) {
|
|
4470
|
+
context = await handler.connectFromRemote(connectionCommand, previousCwd, currentContext);
|
|
4091
4471
|
}
|
|
4092
4472
|
else {
|
|
4093
4473
|
throw new Error(`Nested connections not supported by ${type} handler`);
|
|
@@ -4095,7 +4475,7 @@ Create once, run many times across different machines.`;
|
|
|
4095
4475
|
}
|
|
4096
4476
|
else {
|
|
4097
4477
|
// First level: connect from local
|
|
4098
|
-
context = await
|
|
4478
|
+
context = await handler.connect(connectionCommand, previousCwd);
|
|
4099
4479
|
}
|
|
4100
4480
|
// Push to stacks
|
|
4101
4481
|
this.connectionCommandStack.push(connectionCommand);
|
|
@@ -4167,9 +4547,8 @@ Create once, run many times across different machines.`;
|
|
|
4167
4547
|
}
|
|
4168
4548
|
return;
|
|
4169
4549
|
}
|
|
4170
|
-
// No remote context - show regular confirmation
|
|
4171
4550
|
// No remote context - show regular confirmation and restore CWD
|
|
4172
|
-
if (chat.cwd && !chat.remoteContext) {
|
|
4551
|
+
if (!shouldPreserveRemote && chat.cwd && !chat.remoteContext) {
|
|
4173
4552
|
if (fs.existsSync(chat.cwd)) {
|
|
4174
4553
|
this.cwd = chat.cwd;
|
|
4175
4554
|
this.contextManager.updateWorkingDirectory(chat.cwd);
|
|
@@ -4178,7 +4557,8 @@ Create once, run many times across different machines.`;
|
|
|
4178
4557
|
}
|
|
4179
4558
|
}
|
|
4180
4559
|
}
|
|
4181
|
-
if (
|
|
4560
|
+
// Skip loaded message if called from revert (to avoid React setState race condition)
|
|
4561
|
+
if (this.onDirectMessageCallback && !skipLoadedMessage) {
|
|
4182
4562
|
const responseMessage = `✅ Loaded chat: "${chat.title}"\n\nYou have ${chat.messageCount} messages in AI context. Continue your conversation!`;
|
|
4183
4563
|
this.onDirectMessageCallback(responseMessage);
|
|
4184
4564
|
}
|
|
@@ -4210,12 +4590,19 @@ Create once, run many times across different machines.`;
|
|
|
4210
4590
|
* Start a new chat session
|
|
4211
4591
|
*/
|
|
4212
4592
|
startNewChat() {
|
|
4593
|
+
const currentContext = this.contextManager.getCurrentContext();
|
|
4213
4594
|
this.conversationHistory = [];
|
|
4214
4595
|
this.currentChatId = null;
|
|
4215
4596
|
this.conversationStarted = false;
|
|
4216
4597
|
this.uiMessageHistory = [];
|
|
4217
|
-
|
|
4218
|
-
|
|
4598
|
+
// Only clear stacks if we're in local context
|
|
4599
|
+
// If we're in a remote context (SSH/WSL/Docker), we want to PRESERVE the connection state
|
|
4600
|
+
// so the new chat continues in the same environment
|
|
4601
|
+
if (currentContext.type === 'local') {
|
|
4602
|
+
this.cwdStack = [];
|
|
4603
|
+
this.connectionCommandStack = [];
|
|
4604
|
+
}
|
|
4605
|
+
// Else: Preserve cwdStack and connectionCommandStack for remote sessions
|
|
4219
4606
|
// Reset context limit state
|
|
4220
4607
|
if (this.contextLimitReached) {
|
|
4221
4608
|
this.contextLimitReached = false;
|
|
@@ -4256,6 +4643,12 @@ Create once, run many times across different machines.`;
|
|
|
4256
4643
|
setOnRestoreMessagesCallback(callback) {
|
|
4257
4644
|
this.onRestoreMessagesCallback = callback;
|
|
4258
4645
|
}
|
|
4646
|
+
/**
|
|
4647
|
+
* Set callback for setting input value (e.g., for revert)
|
|
4648
|
+
*/
|
|
4649
|
+
setOnSetInput(callback) {
|
|
4650
|
+
this.onSetInputCallback = callback;
|
|
4651
|
+
}
|
|
4259
4652
|
/**
|
|
4260
4653
|
* Get environment context for backend
|
|
4261
4654
|
* Returns structured environment information to be sent to backend
|