centaurus-cli 3.1.2 → 3.1.4

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 (142) hide show
  1. package/dist/cli-adapter.js +689 -155
  2. package/dist/cli-adapter.js.map +1 -1
  3. package/dist/config/defaultConfig.js +1 -4
  4. package/dist/config/defaultConfig.js.map +1 -1
  5. package/dist/config/models.js +6 -0
  6. package/dist/config/models.js.map +1 -1
  7. package/dist/config/slash-commands.js +66 -2
  8. package/dist/config/slash-commands.js.map +1 -1
  9. package/dist/config/types.js +4 -4
  10. package/dist/config/types.js.map +1 -1
  11. package/dist/index.js +36 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/services/ai-context-injector.js +109 -0
  14. package/dist/services/ai-context-injector.js.map +1 -1
  15. package/dist/services/ai-service-client.js +3 -2
  16. package/dist/services/ai-service-client.js.map +1 -1
  17. package/dist/services/api-client.js.map +1 -1
  18. package/dist/services/background-task-manager.js +59 -0
  19. package/dist/services/background-task-manager.js.map +1 -1
  20. package/dist/services/local-chat-storage.js +2 -0
  21. package/dist/services/local-chat-storage.js.map +1 -1
  22. package/dist/services/skill-storage.js +141 -0
  23. package/dist/services/skill-storage.js.map +1 -0
  24. package/dist/services/sub-agent-manager.js +49 -8
  25. package/dist/services/sub-agent-manager.js.map +1 -1
  26. package/dist/services/warpify-detector.js +17 -5
  27. package/dist/services/warpify-detector.js.map +1 -1
  28. package/dist/tools/background-command.js +5 -2
  29. package/dist/tools/background-command.js.map +1 -1
  30. package/dist/tools/command.js +367 -109
  31. package/dist/tools/command.js.map +1 -1
  32. package/dist/tools/file-ops.js +23 -6
  33. package/dist/tools/file-ops.js.map +1 -1
  34. package/dist/tools/plan-mode.js +184 -336
  35. package/dist/tools/plan-mode.js.map +1 -1
  36. package/dist/tools/sub-agent.js +24 -5
  37. package/dist/tools/sub-agent.js.map +1 -1
  38. package/dist/tools/todo-list.js +157 -0
  39. package/dist/tools/todo-list.js.map +1 -0
  40. package/dist/types/skill.js +30 -0
  41. package/dist/types/skill.js.map +1 -0
  42. package/dist/ui/components/App.js +956 -162
  43. package/dist/ui/components/App.js.map +1 -1
  44. package/dist/ui/components/AuthScreen.js +3 -1
  45. package/dist/ui/components/AuthScreen.js.map +1 -1
  46. package/dist/ui/components/AuthWelcomeScreen.js +3 -1
  47. package/dist/ui/components/AuthWelcomeScreen.js.map +1 -1
  48. package/dist/ui/components/CodeBlock.js +3 -1
  49. package/dist/ui/components/CodeBlock.js.map +1 -1
  50. package/dist/ui/components/CompactShellPreview.js +44 -0
  51. package/dist/ui/components/CompactShellPreview.js.map +1 -0
  52. package/dist/ui/components/ConfigViewer.js +3 -1
  53. package/dist/ui/components/ConfigViewer.js.map +1 -1
  54. package/dist/ui/components/ConfirmPrompt.js +3 -1
  55. package/dist/ui/components/ConfirmPrompt.js.map +1 -1
  56. package/dist/ui/components/ConnectionStatusMessage.js +3 -1
  57. package/dist/ui/components/ConnectionStatusMessage.js.map +1 -1
  58. package/dist/ui/components/DetailedPlanReviewScreen.js +84 -74
  59. package/dist/ui/components/DetailedPlanReviewScreen.js.map +1 -1
  60. package/dist/ui/components/DiffViewer.js +6 -3
  61. package/dist/ui/components/DiffViewer.js.map +1 -1
  62. package/dist/ui/components/FileCreationPreview.js.map +1 -1
  63. package/dist/ui/components/FileTagAutocomplete.js +4 -2
  64. package/dist/ui/components/FileTagAutocomplete.js.map +1 -1
  65. package/dist/ui/components/InputBox.js +243 -40
  66. package/dist/ui/components/InputBox.js.map +1 -1
  67. package/dist/ui/components/InteractiveShell.js +5 -3
  68. package/dist/ui/components/InteractiveShell.js.map +1 -1
  69. package/dist/ui/components/KeyboardHelp.js +4 -1
  70. package/dist/ui/components/KeyboardHelp.js.map +1 -1
  71. package/dist/ui/components/LoadingIndicator.js +3 -1
  72. package/dist/ui/components/LoadingIndicator.js.map +1 -1
  73. package/dist/ui/components/MCPAddScreen.js +63 -13
  74. package/dist/ui/components/MCPAddScreen.js.map +1 -1
  75. package/dist/ui/components/MarkdownRenderer.js +3 -1
  76. package/dist/ui/components/MarkdownRenderer.js.map +1 -1
  77. package/dist/ui/components/MessageDisplay.js +9 -7
  78. package/dist/ui/components/MessageDisplay.js.map +1 -1
  79. package/dist/ui/components/ModelPicker.js +170 -0
  80. package/dist/ui/components/ModelPicker.js.map +1 -0
  81. package/dist/ui/components/MonitorModeAIPanel.js +3 -1
  82. package/dist/ui/components/MonitorModeAIPanel.js.map +1 -1
  83. package/dist/ui/components/PlanAcceptedMessage.js +12 -6
  84. package/dist/ui/components/PlanAcceptedMessage.js.map +1 -1
  85. package/dist/ui/components/PlanQuestionMessage.js +37 -0
  86. package/dist/ui/components/PlanQuestionMessage.js.map +1 -0
  87. package/dist/ui/components/PlanQuestionScreen.js +138 -0
  88. package/dist/ui/components/PlanQuestionScreen.js.map +1 -0
  89. package/dist/ui/components/PlanReviewScreen.js +7 -9
  90. package/dist/ui/components/PlanReviewScreen.js.map +1 -1
  91. package/dist/ui/components/RulesEditorScreen.js +65 -28
  92. package/dist/ui/components/RulesEditorScreen.js.map +1 -1
  93. package/dist/ui/components/SelectPrompt.js +3 -1
  94. package/dist/ui/components/SelectPrompt.js.map +1 -1
  95. package/dist/ui/components/SkillCreatorScreen.js +217 -0
  96. package/dist/ui/components/SkillCreatorScreen.js.map +1 -0
  97. package/dist/ui/components/SlashCommandAutocomplete.js +4 -2
  98. package/dist/ui/components/SlashCommandAutocomplete.js.map +1 -1
  99. package/dist/ui/components/StatusBar.js +4 -2
  100. package/dist/ui/components/StatusBar.js.map +1 -1
  101. package/dist/ui/components/StreamingMessageDisplay.js +5 -3
  102. package/dist/ui/components/StreamingMessageDisplay.js.map +1 -1
  103. package/dist/ui/components/SubAgentListScreen.js +65 -0
  104. package/dist/ui/components/SubAgentListScreen.js.map +1 -0
  105. package/dist/ui/components/SubAgentViewScreen.js +123 -0
  106. package/dist/ui/components/SubAgentViewScreen.js.map +1 -0
  107. package/dist/ui/components/TaskCompletedMessage.js +40 -8
  108. package/dist/ui/components/TaskCompletedMessage.js.map +1 -1
  109. package/dist/ui/components/TaskProgressIndicator.js +6 -4
  110. package/dist/ui/components/TaskProgressIndicator.js.map +1 -1
  111. package/dist/ui/components/TextEditor.js +297 -0
  112. package/dist/ui/components/TextEditor.js.map +1 -0
  113. package/dist/ui/components/TodoListMessage.js +59 -0
  114. package/dist/ui/components/TodoListMessage.js.map +1 -0
  115. package/dist/ui/components/ToolExecutionMessage.js +134 -84
  116. package/dist/ui/components/ToolExecutionMessage.js.map +1 -1
  117. package/dist/ui/components/ToolExecutionStatus.js +3 -1
  118. package/dist/ui/components/ToolExecutionStatus.js.map +1 -1
  119. package/dist/ui/components/WelcomeBanner.js +33 -33
  120. package/dist/ui/components/WelcomeBanner.js.map +1 -1
  121. package/dist/ui/components/WorkflowCreatorScreen.js +5 -3
  122. package/dist/ui/components/WorkflowCreatorScreen.js.map +1 -1
  123. package/dist/ui/theme.js +97 -0
  124. package/dist/ui/theme.js.map +1 -0
  125. package/dist/ui/utils/chat-history-limit.js +247 -0
  126. package/dist/ui/utils/chat-history-limit.js.map +1 -0
  127. package/dist/utils/chat-formatter.js +22 -9
  128. package/dist/utils/chat-formatter.js.map +1 -1
  129. package/dist/utils/git-stats.js +7 -5
  130. package/dist/utils/git-stats.js.map +1 -1
  131. package/dist/utils/input-classifier.js +11 -1
  132. package/dist/utils/input-classifier.js.map +1 -1
  133. package/dist/utils/output-truncation.js +175 -0
  134. package/dist/utils/output-truncation.js.map +1 -0
  135. package/dist/utils/rule-reference-resolver.js +3 -3
  136. package/dist/utils/rule-reference-resolver.js.map +1 -1
  137. package/dist/utils/tunnel-commands-manager.js +134 -0
  138. package/dist/utils/tunnel-commands-manager.js.map +1 -0
  139. package/package.json +91 -90
  140. package/postinstall.js +4 -11
  141. package/dist/ui/components/MultiLineInput.js +0 -255
  142. package/dist/ui/components/MultiLineInput.js.map +0 -1
@@ -2,6 +2,63 @@ import * as shellUtils from "../utils/shell.js";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import { runWSLCommand, runDockerCommand, runSSHCommand } from "../utils/editor-utils.js";
5
+ import { BackgroundTaskManager } from "../services/background-task-manager.js";
6
+ import { quickLog } from "../utils/conversation-logger.js";
7
+ function extractCdTargetsFromCommand(command) {
8
+ const targets = [];
9
+ const segments = command.split(/\s*(?:&&|\|\||;)\s*/);
10
+ for (const segment of segments) {
11
+ const trimmed = segment.trim();
12
+ if (!trimmed) continue;
13
+ let match = null;
14
+ match = trimmed.match(/^cd\s+\/d\s+(.+)$/i);
15
+ if (!match) {
16
+ match = trimmed.match(/^cd\s+(.+)$/i);
17
+ }
18
+ if (!match) {
19
+ match = trimmed.match(/^pushd\s+(.+)$/i);
20
+ }
21
+ if (!match) {
22
+ match = trimmed.match(/^Set-Location\s+(.+)$/i);
23
+ }
24
+ if (!match) {
25
+ match = trimmed.match(/^sl\s+(.+)$/i);
26
+ }
27
+ if (!match) {
28
+ match = trimmed.match(/^chdir\s+(.+)$/i);
29
+ }
30
+ if (match) {
31
+ let target = match[1].trim();
32
+ target = target.replace(/^["']|["']$/g, "");
33
+ if (target.length > 1) {
34
+ target = target.replace(/[\/\\]+$/, "");
35
+ }
36
+ targets.push(target);
37
+ }
38
+ }
39
+ return targets;
40
+ }
41
+ function resolvePostCommandCwd(currentCwd, command, isWindows = false) {
42
+ const targets = extractCdTargetsFromCommand(command);
43
+ if (targets.length === 0) return null;
44
+ let cwd = currentCwd;
45
+ for (const target of targets) {
46
+ let expanded = target;
47
+ if (expanded === "~" || expanded.startsWith("~/") || expanded.startsWith("~\\")) {
48
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
49
+ expanded = homeDir + expanded.substring(1);
50
+ }
51
+ if (expanded === "-") continue;
52
+ cwd = path.resolve(cwd, expanded);
53
+ }
54
+ try {
55
+ if (fs.existsSync(cwd) && fs.statSync(cwd).isDirectory()) {
56
+ return cwd;
57
+ }
58
+ } catch {
59
+ }
60
+ return null;
61
+ }
5
62
  function processShellInput(input) {
6
63
  let processed = input.replace(/\\n/g, "\r").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\b/g, "\b").replace(/\\x1B/g, "\x1B");
7
64
  const keyMap = {
@@ -33,6 +90,25 @@ function resolveCommandWorkingDirectory(currentContext, contextCwd, commandCwd)
33
90
  }
34
91
  return path.posix.isAbsolute(commandCwd) ? commandCwd : path.posix.join(remoteBase, commandCwd);
35
92
  }
93
+ const TIMEOUT_TRANSFER_PREFIX = "__TIMEOUT_TRANSFER__";
94
+ function buildTransferMessage(taskId, command, cwd, timeoutSeconds, outputSoFar, remoteContext) {
95
+ const outputPreview = outputSoFar.length > 500 ? outputSoFar.slice(-500) : outputSoFar;
96
+ const location = remoteContext ? ` (on ${remoteContext})` : "";
97
+ return `Command exceeded timeout of ${timeoutSeconds}s and has been transferred to background${location}.
98
+
99
+ **Background Task ID:** ${taskId}
100
+ **Command:** ${command}
101
+ **Working Directory:** ${cwd}
102
+ **Status:** Still running in background
103
+
104
+ **Output so far (last 500 chars):**
105
+ \`\`\`
106
+ ${outputPreview || "(no output yet)"}
107
+ \`\`\`
108
+
109
+ Use \`background_command\` with action="status" and task_id="${taskId}" to check progress.
110
+ Use \`background_command\` with action="kill" and task_id="${taskId}" to terminate it.`;
111
+ }
36
112
  const runCommandTool = {
37
113
  schema: {
38
114
  name: "execute_command",
@@ -71,13 +147,48 @@ Commands will be run with PAGER=cat. You may want to limit the length of output
71
147
  shell_input: {
72
148
  type: "string",
73
149
  description: "[INTERNAL - DO NOT USE FROM MAIN AGENT] Input to send to a CURRENTLY RUNNING interactive shell. This parameter is used internally by the shell input sub-agent. When starting a command, do NOT set this parameter - just start the command and the user will provide input through the shell UI or agent control mode."
150
+ },
151
+ timeout_seconds: {
152
+ type: "integer",
153
+ description: "Optional timeout in seconds. If the command takes longer than this, it will be automatically transferred to a background shell and you will receive the background task ID to monitor it. Use this for commands that might take a while (e.g., builds, installs, tests) where you want to continue working if they run long. If not set, the command runs until completion with no timeout."
74
154
  }
75
155
  },
76
156
  required: ["reason_text", "CommandLine", "Cwd", "SafeToAutoRun", "WaitMsBeforeAsync"]
77
157
  }
78
158
  },
79
159
  async execute(args, context) {
80
- const { CommandLine, Cwd, shell_input } = args;
160
+ const { CommandLine, Cwd, shell_input, timeout_seconds } = args;
161
+ const timeoutMs = timeout_seconds && timeout_seconds > 0 ? timeout_seconds * 1e3 : 0;
162
+ if (!shell_input && context.cliAdapter && context.cliAdapter.isCustomTunnelActive()) {
163
+ const interactiveProcess = context.cliAdapter.getCurrentInteractiveProcess();
164
+ if (interactiveProcess && CommandLine) {
165
+ const inputWithEnter = CommandLine.endsWith("\n") || CommandLine.endsWith("\r") ? CommandLine : CommandLine + "\r";
166
+ const processedInput = processShellInput(inputWithEnter);
167
+ interactiveProcess.write(processedInput);
168
+ const tunnelBuf = () => context.cliAdapter.tunnelOutputBuffer || "";
169
+ const beforeLen = tunnelBuf().length;
170
+ let lastLen = beforeLen;
171
+ let stableTime = 0;
172
+ const waitStart = Date.now();
173
+ while (Date.now() - waitStart < 5e3) {
174
+ await new Promise((r) => setTimeout(r, 300));
175
+ const curLen = tunnelBuf().length;
176
+ if (curLen > lastLen) {
177
+ lastLen = curLen;
178
+ stableTime = 0;
179
+ } else {
180
+ stableTime += 300;
181
+ if (stableTime >= 800) break;
182
+ }
183
+ }
184
+ const captured = tunnelBuf().substring(beforeLen);
185
+ const { processTerminalOutput } = await import("../utils/terminal-output.js");
186
+ return processTerminalOutput(captured).trim() || "(command sent, no output captured)";
187
+ }
188
+ if (!interactiveProcess) {
189
+ context.cliAdapter.customTunnelCommand = null;
190
+ }
191
+ }
81
192
  if (shell_input) {
82
193
  if (context.cliAdapter) {
83
194
  const interactiveProcess = context.cliAdapter.getCurrentInteractiveProcess();
@@ -223,69 +334,124 @@ Command that needs background execution: ${command}`
223
334
  return new Promise((resolve, reject) => {
224
335
  let output = "";
225
336
  let exitCode;
226
- const interactiveProcess = shellUtils.executeCommandInteractive(
227
- command,
228
- executionCwd,
229
- (data) => {
230
- output += data;
231
- if (context.onStreamingOutput) {
232
- context.onStreamingOutput(data, "stdout");
233
- }
234
- },
235
- (code) => {
236
- exitCode = code;
237
- if (context.cliAdapter) {
238
- context.cliAdapter.setCurrentInteractiveProcess(void 0);
337
+ let transferred = false;
338
+ let timeoutTimer = null;
339
+ let onDataCallback = (data) => {
340
+ output += data;
341
+ if (context.onStreamingOutput) {
342
+ context.onStreamingOutput(data, "stdout");
343
+ }
344
+ };
345
+ let onExitCallback = (code) => {
346
+ exitCode = code;
347
+ if (timeoutTimer) {
348
+ clearTimeout(timeoutTimer);
349
+ timeoutTimer = null;
350
+ }
351
+ if (transferred) return;
352
+ if (context.cliAdapter) {
353
+ context.cliAdapter.setCurrentInteractiveProcess(void 0);
354
+ }
355
+ if (code === 0) {
356
+ const newCwd = resolvePostCommandCwd(executionCwd, command);
357
+ if (newCwd && newCwd !== executionCwd && context.cliAdapter) {
358
+ context.cliAdapter.cwd = newCwd;
359
+ contextManager.updateWorkingDirectory(newCwd);
360
+ if (context.cliAdapter.onCwdChange) {
361
+ context.cliAdapter.onCwdChange(newCwd);
362
+ }
239
363
  }
240
- if (code === 0) {
241
- resolve(output || "Command executed successfully");
242
- } else {
243
- const errorOutput = output + `
364
+ resolve(output || "Command executed successfully");
365
+ } else {
366
+ const errorOutput = output + `
244
367
  Exit Code: ${code}`;
245
- reject(new Error(`${errorOutput || `Command failed with exit code ${code}`}
368
+ reject(new Error(`${errorOutput || `Command failed with exit code ${code}`}
246
369
 
247
370
  Possible reasons:
248
371
  1. The command is invalid or not installed.
249
372
  2. The arguments are incorrect.
250
373
  3. The command requires user input (interactive mode).`));
251
- }
252
374
  }
375
+ };
376
+ const interactiveProcess = shellUtils.executeCommandInteractive(
377
+ command,
378
+ executionCwd,
379
+ (data) => onDataCallback(data),
380
+ (code) => onExitCallback(code)
253
381
  );
254
382
  if (context.cliAdapter) {
255
383
  context.cliAdapter.setCurrentInteractiveProcess(interactiveProcess);
256
384
  }
385
+ if (timeoutMs > 0) {
386
+ timeoutTimer = setTimeout(() => {
387
+ if (transferred || exitCode !== void 0) return;
388
+ transferred = true;
389
+ args.timeoutTransferred = true;
390
+ quickLog(`[${(/* @__PURE__ */ new Date()).toISOString()}] [execute_command] Timeout (${timeout_seconds}s) exceeded for: ${command}. Transferring to background.
391
+ `);
392
+ const adopted = BackgroundTaskManager.adoptRunningProcess(
393
+ command,
394
+ executionCwd,
395
+ output
396
+ );
397
+ const remotePty = {
398
+ write: (data) => interactiveProcess.write(data),
399
+ kill: () => interactiveProcess.kill(),
400
+ resize: (cols, rows) => {
401
+ if (interactiveProcess.resize) interactiveProcess.resize(cols, rows);
402
+ },
403
+ isRunning: () => interactiveProcess.ptyProcess?.isRunning?.() ?? false
404
+ };
405
+ adopted.setRemotePty(remotePty);
406
+ onDataCallback = adopted.onData;
407
+ onExitCallback = adopted.onExit;
408
+ if (context.cliAdapter) {
409
+ context.cliAdapter.setCurrentInteractiveProcess(void 0);
410
+ }
411
+ resolve(buildTransferMessage(adopted.id, command, executionCwd, timeout_seconds, output));
412
+ }, timeoutMs);
413
+ }
257
414
  });
258
415
  } else if (currentContext.type === "wsl") {
259
416
  const remoteCwd = executionCwd;
260
417
  const distribution = currentContext.metadata?.distroName || "Ubuntu";
261
418
  return new Promise((resolve, reject) => {
262
419
  let output = "";
420
+ let transferred = false;
421
+ let timeoutTimer = null;
263
422
  const constrainedCols = Math.max(40, (process.stdout.columns || 80) - 4);
264
423
  const constrainedRows = Math.min(process.stdout.rows || 24, 50);
265
- const wslPty = runWSLCommand(
266
- distribution,
267
- command,
268
- remoteCwd,
269
- (data) => {
270
- output += data;
271
- if (context.onStreamingOutput) {
272
- context.onStreamingOutput(data, "stdout");
273
- }
274
- },
275
- (exitCode) => {
276
- if (context.cliAdapter) {
277
- context.cliAdapter.setCurrentInteractiveProcess(void 0);
278
- }
279
- if (exitCode !== 0) {
280
- reject(new Error(`${output || `Command failed with exit code ${exitCode}`}
424
+ let onDataCallback = (data) => {
425
+ output += data;
426
+ if (context.onStreamingOutput) {
427
+ context.onStreamingOutput(data, "stdout");
428
+ }
429
+ };
430
+ let onExitCallback = (exitCode) => {
431
+ if (timeoutTimer) {
432
+ clearTimeout(timeoutTimer);
433
+ timeoutTimer = null;
434
+ }
435
+ if (transferred) return;
436
+ if (context.cliAdapter) {
437
+ context.cliAdapter.setCurrentInteractiveProcess(void 0);
438
+ }
439
+ if (exitCode !== 0) {
440
+ reject(new Error(`${output || `Command failed with exit code ${exitCode}`}
281
441
 
282
442
  Possible reasons:
283
443
  1. The command is invalid or not installed.
284
444
  2. The arguments are incorrect.`));
285
- } else {
286
- resolve(output || "Command executed successfully");
287
- }
288
- },
445
+ } else {
446
+ resolve(output || "Command executed successfully");
447
+ }
448
+ };
449
+ const wslPty = runWSLCommand(
450
+ distribution,
451
+ command,
452
+ remoteCwd,
453
+ (data) => onDataCallback(data),
454
+ (exitCode) => onExitCallback(exitCode),
289
455
  constrainedCols,
290
456
  constrainedRows
291
457
  );
@@ -303,6 +469,24 @@ Possible reasons:
303
469
  isPty: true
304
470
  });
305
471
  }
472
+ if (timeoutMs > 0) {
473
+ timeoutTimer = setTimeout(() => {
474
+ if (transferred) return;
475
+ transferred = true;
476
+ args.timeoutTransferred = true;
477
+ const adopted = BackgroundTaskManager.adoptRunningProcess(command, remoteCwd, output, `wsl:${distribution}`);
478
+ adopted.setRemotePty({
479
+ write: (data) => wslPty.write(data),
480
+ kill: () => wslPty.kill(),
481
+ resize: (cols, rows) => wslPty.resize(cols, rows),
482
+ isRunning: () => wslPty.isRunning()
483
+ });
484
+ onDataCallback = adopted.onData;
485
+ onExitCallback = adopted.onExit;
486
+ if (context.cliAdapter) context.cliAdapter.setCurrentInteractiveProcess(void 0);
487
+ resolve(buildTransferMessage(adopted.id, command, remoteCwd, timeout_seconds, output, `wsl:${distribution}`));
488
+ }, timeoutMs);
489
+ }
306
490
  });
307
491
  } else if (currentContext.type === "docker") {
308
492
  const parentContext = contextManager.getParentContext();
@@ -317,33 +501,41 @@ Possible reasons:
317
501
  }
318
502
  const escapedCommand = command.replace(/"/g, '\\"');
319
503
  const dockerCommand = `docker exec -w "${remoteCwd}" ${containerId} sh -c "${escapedCommand}"`;
504
+ const remoteLabel = `docker:${containerId.substring(0, 12)} via ssh`;
320
505
  return new Promise((resolve, reject) => {
321
506
  let output = "";
322
- const sshPty = runSSHCommand(
323
- sshClient,
324
- dockerCommand,
325
- parentContext.metadata.workingDirectory || "~",
326
- (data) => {
327
- output += data;
328
- if (context.onStreamingOutput) {
329
- context.onStreamingOutput(data, "stdout");
330
- }
331
- },
332
- (exitCode) => {
333
- if (context.cliAdapter) {
334
- context.cliAdapter.setCurrentInteractiveProcess(void 0);
335
- }
336
- if (exitCode !== 0) {
337
- reject(new Error(`${output || `Command failed with exit code ${exitCode}`}
507
+ let transferred = false;
508
+ let timeoutTimer = null;
509
+ let onDataCallback = (data) => {
510
+ output += data;
511
+ if (context.onStreamingOutput) {
512
+ context.onStreamingOutput(data, "stdout");
513
+ }
514
+ };
515
+ let onExitCallback = (exitCode) => {
516
+ if (timeoutTimer) {
517
+ clearTimeout(timeoutTimer);
518
+ timeoutTimer = null;
519
+ }
520
+ if (transferred) return;
521
+ if (context.cliAdapter) context.cliAdapter.setCurrentInteractiveProcess(void 0);
522
+ if (exitCode !== 0) {
523
+ reject(new Error(`${output || `Command failed with exit code ${exitCode}`}
338
524
 
339
525
  Possible reasons:
340
526
  1. The command is invalid or not installed in the container.
341
527
  2. The arguments are incorrect.
342
528
  3. The Docker container may not be running.`));
343
- } else {
344
- resolve(output || "Command executed successfully");
345
- }
346
- },
529
+ } else {
530
+ resolve(output || "Command executed successfully");
531
+ }
532
+ };
533
+ const sshPty = runSSHCommand(
534
+ sshClient,
535
+ dockerCommand,
536
+ parentContext.metadata.workingDirectory || "~",
537
+ (data) => onDataCallback(data),
538
+ (exitCode) => onExitCallback(exitCode),
347
539
  constrainedCols,
348
540
  constrainedRows
349
541
  );
@@ -353,42 +545,66 @@ Possible reasons:
353
545
  write: (data) => sshPty.write(data),
354
546
  kill: () => sshPty.kill(),
355
547
  signal: (sig) => {
356
- if (sig === "SIGINT") {
357
- sshPty.write("");
358
- }
548
+ if (sig === "SIGINT") sshPty.write("");
359
549
  },
360
550
  resize: (cols, rows) => sshPty.resize(cols, rows),
361
551
  isPty: true
362
552
  });
363
553
  }
554
+ if (timeoutMs > 0) {
555
+ timeoutTimer = setTimeout(() => {
556
+ if (transferred) return;
557
+ transferred = true;
558
+ args.timeoutTransferred = true;
559
+ const adopted = BackgroundTaskManager.adoptRunningProcess(command, remoteCwd, output, remoteLabel);
560
+ adopted.setRemotePty({
561
+ write: (data) => sshPty.write(data),
562
+ kill: () => sshPty.kill(),
563
+ resize: (cols, rows) => sshPty.resize(cols, rows),
564
+ isRunning: () => sshPty.isRunning()
565
+ });
566
+ onDataCallback = adopted.onData;
567
+ onExitCallback = adopted.onExit;
568
+ if (context.cliAdapter) context.cliAdapter.setCurrentInteractiveProcess(void 0);
569
+ resolve(buildTransferMessage(adopted.id, command, remoteCwd, timeout_seconds, output, remoteLabel));
570
+ }, timeoutMs);
571
+ }
364
572
  });
365
573
  } else {
574
+ const dockerLabel = `docker:${containerId.substring(0, 12)}`;
366
575
  return new Promise((resolve, reject) => {
367
576
  let output = "";
368
- const dockerPty = runDockerCommand(
369
- containerId,
370
- command,
371
- remoteCwd,
372
- (data) => {
373
- output += data;
374
- if (context.onStreamingOutput) {
375
- context.onStreamingOutput(data, "stdout");
376
- }
377
- },
378
- (exitCode) => {
379
- if (context.cliAdapter) {
380
- context.cliAdapter.setCurrentInteractiveProcess(void 0);
381
- }
382
- if (exitCode !== 0) {
383
- reject(new Error(`${output || `Command failed with exit code ${exitCode}`}
577
+ let transferred = false;
578
+ let timeoutTimer = null;
579
+ let onDataCallback = (data) => {
580
+ output += data;
581
+ if (context.onStreamingOutput) {
582
+ context.onStreamingOutput(data, "stdout");
583
+ }
584
+ };
585
+ let onExitCallback = (exitCode) => {
586
+ if (timeoutTimer) {
587
+ clearTimeout(timeoutTimer);
588
+ timeoutTimer = null;
589
+ }
590
+ if (transferred) return;
591
+ if (context.cliAdapter) context.cliAdapter.setCurrentInteractiveProcess(void 0);
592
+ if (exitCode !== 0) {
593
+ reject(new Error(`${output || `Command failed with exit code ${exitCode}`}
384
594
 
385
595
  Possible reasons:
386
596
  1. The command is invalid or not installed.
387
597
  2. The arguments are incorrect.`));
388
- } else {
389
- resolve(output || "Command executed successfully");
390
- }
391
- },
598
+ } else {
599
+ resolve(output || "Command executed successfully");
600
+ }
601
+ };
602
+ const dockerPty = runDockerCommand(
603
+ containerId,
604
+ command,
605
+ remoteCwd,
606
+ (data) => onDataCallback(data),
607
+ (exitCode) => onExitCallback(exitCode),
392
608
  constrainedCols,
393
609
  constrainedRows
394
610
  );
@@ -398,14 +614,30 @@ Possible reasons:
398
614
  write: (data) => dockerPty.write(data),
399
615
  kill: () => dockerPty.kill(),
400
616
  signal: (sig) => {
401
- if (sig === "SIGINT") {
402
- dockerPty.write("");
403
- }
617
+ if (sig === "SIGINT") dockerPty.write("");
404
618
  },
405
619
  resize: (cols, rows) => dockerPty.resize(cols, rows),
406
620
  isPty: true
407
621
  });
408
622
  }
623
+ if (timeoutMs > 0) {
624
+ timeoutTimer = setTimeout(() => {
625
+ if (transferred) return;
626
+ transferred = true;
627
+ args.timeoutTransferred = true;
628
+ const adopted = BackgroundTaskManager.adoptRunningProcess(command, remoteCwd, output, dockerLabel);
629
+ adopted.setRemotePty({
630
+ write: (data) => dockerPty.write(data),
631
+ kill: () => dockerPty.kill(),
632
+ resize: (cols, rows) => dockerPty.resize(cols, rows),
633
+ isRunning: () => dockerPty.isRunning()
634
+ });
635
+ onDataCallback = adopted.onData;
636
+ onExitCallback = adopted.onExit;
637
+ if (context.cliAdapter) context.cliAdapter.setCurrentInteractiveProcess(void 0);
638
+ resolve(buildTransferMessage(adopted.id, command, remoteCwd, timeout_seconds, output, dockerLabel));
639
+ }, timeoutMs);
640
+ }
409
641
  });
410
642
  }
411
643
  } else if (currentContext.type === "ssh") {
@@ -414,34 +646,42 @@ Possible reasons:
414
646
  if (!sshClient) {
415
647
  throw new Error("SSH client not available");
416
648
  }
649
+ const sshLabel = `${currentContext.metadata?.username || "user"}@${currentContext.metadata?.hostname || "remote"}`;
417
650
  return new Promise((resolve, reject) => {
418
651
  let output = "";
652
+ let transferred = false;
653
+ let timeoutTimer = null;
419
654
  const constrainedCols = Math.max(40, (process.stdout.columns || 80) - 4);
420
655
  const constrainedRows = Math.min(process.stdout.rows || 24, 50);
421
- const sshPty = runSSHCommand(
422
- sshClient,
423
- command,
424
- remoteCwd,
425
- (data) => {
426
- output += data;
427
- if (context.onStreamingOutput) {
428
- context.onStreamingOutput(data, "stdout");
429
- }
430
- },
431
- (exitCode) => {
432
- if (context.cliAdapter) {
433
- context.cliAdapter.setCurrentInteractiveProcess(void 0);
434
- }
435
- if (exitCode !== 0) {
436
- reject(new Error(`${output || `Command failed with exit code ${exitCode}`}
656
+ let onDataCallback = (data) => {
657
+ output += data;
658
+ if (context.onStreamingOutput) {
659
+ context.onStreamingOutput(data, "stdout");
660
+ }
661
+ };
662
+ let onExitCallback = (exitCode) => {
663
+ if (timeoutTimer) {
664
+ clearTimeout(timeoutTimer);
665
+ timeoutTimer = null;
666
+ }
667
+ if (transferred) return;
668
+ if (context.cliAdapter) context.cliAdapter.setCurrentInteractiveProcess(void 0);
669
+ if (exitCode !== 0) {
670
+ reject(new Error(`${output || `Command failed with exit code ${exitCode}`}
437
671
 
438
672
  Possible reasons:
439
673
  1. The command is invalid or not installed.
440
674
  2. The arguments are incorrect.`));
441
- } else {
442
- resolve(output || "Command executed successfully");
443
- }
444
- },
675
+ } else {
676
+ resolve(output || "Command executed successfully");
677
+ }
678
+ };
679
+ const sshPty = runSSHCommand(
680
+ sshClient,
681
+ command,
682
+ remoteCwd,
683
+ (data) => onDataCallback(data),
684
+ (exitCode) => onExitCallback(exitCode),
445
685
  constrainedCols,
446
686
  constrainedRows
447
687
  );
@@ -451,19 +691,37 @@ Possible reasons:
451
691
  write: (data) => sshPty.write(data),
452
692
  kill: () => sshPty.kill(),
453
693
  signal: (sig) => {
454
- if (sig === "SIGINT") {
455
- sshPty.write("");
456
- }
694
+ if (sig === "SIGINT") sshPty.write("");
457
695
  },
458
696
  resize: (cols, rows) => sshPty.resize(cols, rows),
459
697
  isPty: true
460
698
  });
461
699
  }
700
+ if (timeoutMs > 0) {
701
+ timeoutTimer = setTimeout(() => {
702
+ if (transferred) return;
703
+ transferred = true;
704
+ args.timeoutTransferred = true;
705
+ const adopted = BackgroundTaskManager.adoptRunningProcess(command, remoteCwd, output, sshLabel);
706
+ adopted.setRemotePty({
707
+ write: (data) => sshPty.write(data),
708
+ kill: () => sshPty.kill(),
709
+ resize: (cols, rows) => sshPty.resize(cols, rows),
710
+ isRunning: () => sshPty.isRunning()
711
+ });
712
+ onDataCallback = adopted.onData;
713
+ onExitCallback = adopted.onExit;
714
+ if (context.cliAdapter) context.cliAdapter.setCurrentInteractiveProcess(void 0);
715
+ resolve(buildTransferMessage(adopted.id, command, remoteCwd, timeout_seconds, output, sshLabel));
716
+ }, timeoutMs);
717
+ }
462
718
  });
463
719
  }
464
720
  }
465
721
  };
466
722
  export {
723
+ extractCdTargetsFromCommand,
724
+ resolvePostCommandCwd,
467
725
  runCommandTool
468
726
  };
469
727
  //# sourceMappingURL=command.js.map