@hcu-lab.me/mcp-device 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/bootstrap.d.ts +1 -0
- package/dist/bootstrap.js +22 -0
- package/dist/command-manager.d.ts +8 -0
- package/dist/command-manager.js +241 -0
- package/dist/config-field-definitions.d.ts +41 -0
- package/dist/config-field-definitions.js +37 -0
- package/dist/config-manager.d.ts +102 -0
- package/dist/config-manager.js +291 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +10 -0
- package/dist/custom-stdio.d.ts +51 -0
- package/dist/custom-stdio.js +358 -0
- package/dist/data/official-app-ca.pem +12 -0
- package/dist/data/onboarding-prompts.json +70 -0
- package/dist/device/device-state.d.ts +13 -0
- package/dist/device/device-state.js +100 -0
- package/dist/device/device-status.d.ts +81 -0
- package/dist/device/device-status.js +156 -0
- package/dist/device/device.d.ts +16 -0
- package/dist/device/device.js +190 -0
- package/dist/device/execution-engine.d.ts +144 -0
- package/dist/device/execution-engine.js +195 -0
- package/dist/device/gateway-channel.d.ts +48 -0
- package/dist/device/gateway-channel.js +384 -0
- package/dist/device/gateway-config.d.ts +40 -0
- package/dist/device/gateway-config.js +207 -0
- package/dist/device/gateway-identity.d.ts +35 -0
- package/dist/device/gateway-identity.js +178 -0
- package/dist/device/gateway-pairing.d.ts +25 -0
- package/dist/device/gateway-pairing.js +105 -0
- package/dist/device/gateway-secure-transport.d.ts +25 -0
- package/dist/device/gateway-secure-transport.js +117 -0
- package/dist/device/gateway-tool-adapter.d.ts +17 -0
- package/dist/device/gateway-tool-adapter.js +284 -0
- package/dist/device/gateway-url-policy.d.ts +3 -0
- package/dist/device/gateway-url-policy.js +43 -0
- package/dist/device/linux-service.d.ts +70 -0
- package/dist/device/linux-service.js +268 -0
- package/dist/device/official-trust.d.ts +7 -0
- package/dist/device/official-trust.js +49 -0
- package/dist/device/project-inspection.d.ts +1 -0
- package/dist/device/project-inspection.js +180 -0
- package/dist/device/runtime-owner.d.ts +41 -0
- package/dist/device/runtime-owner.js +177 -0
- package/dist/device/windows-dpapi.d.ts +11 -0
- package/dist/device/windows-dpapi.js +67 -0
- package/dist/device/windows-service.d.ts +56 -0
- package/dist/device/windows-service.js +238 -0
- package/dist/error-handlers.d.ts +7 -0
- package/dist/error-handlers.js +15 -0
- package/dist/handlers/edit-search-handlers.d.ts +6 -0
- package/dist/handlers/edit-search-handlers.js +6 -0
- package/dist/handlers/filesystem-handlers.d.ts +38 -0
- package/dist/handlers/filesystem-handlers.js +440 -0
- package/dist/handlers/history-handlers.d.ts +12 -0
- package/dist/handlers/history-handlers.js +67 -0
- package/dist/handlers/index.d.ts +6 -0
- package/dist/handlers/index.js +7 -0
- package/dist/handlers/process-handlers.d.ts +9 -0
- package/dist/handlers/process-handlers.js +15 -0
- package/dist/handlers/search-handlers.d.ts +17 -0
- package/dist/handlers/search-handlers.js +224 -0
- package/dist/handlers/terminal-handlers.d.ts +21 -0
- package/dist/handlers/terminal-handlers.js +35 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +138 -0
- package/dist/mcp-device.d.ts +2 -0
- package/dist/mcp-device.js +7 -0
- package/dist/npm-scripts/remote.d.ts +3 -0
- package/dist/npm-scripts/remote.js +314 -0
- package/dist/search-manager.d.ts +147 -0
- package/dist/search-manager.js +828 -0
- package/dist/server.d.ts +46 -0
- package/dist/server.js +1563 -0
- package/dist/terminal-manager.d.ts +104 -0
- package/dist/terminal-manager.js +635 -0
- package/dist/tools/config.d.ts +96 -0
- package/dist/tools/config.js +260 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +435 -0
- package/dist/tools/feedback.d.ts +8 -0
- package/dist/tools/feedback.js +132 -0
- package/dist/tools/filesystem.d.ts +79 -0
- package/dist/tools/filesystem.js +947 -0
- package/dist/tools/fuzzySearch.d.ts +12 -0
- package/dist/tools/fuzzySearch.js +74 -0
- package/dist/tools/fuzzySearchCore.d.ts +52 -0
- package/dist/tools/fuzzySearchCore.js +125 -0
- package/dist/tools/improved-process-tools.d.ts +24 -0
- package/dist/tools/improved-process-tools.js +627 -0
- package/dist/tools/mime-types.d.ts +3 -0
- package/dist/tools/mime-types.js +28 -0
- package/dist/tools/pdf/extract-images.d.ts +34 -0
- package/dist/tools/pdf/extract-images.js +132 -0
- package/dist/tools/pdf/index.d.ts +6 -0
- package/dist/tools/pdf/index.js +3 -0
- package/dist/tools/pdf/lib/pdf2md.d.ts +36 -0
- package/dist/tools/pdf/lib/pdf2md.js +76 -0
- package/dist/tools/pdf/manipulations.d.ts +13 -0
- package/dist/tools/pdf/manipulations.js +99 -0
- package/dist/tools/pdf/markdown.d.ts +26 -0
- package/dist/tools/pdf/markdown.js +275 -0
- package/dist/tools/pdf/utils.d.ts +12 -0
- package/dist/tools/pdf/utils.js +34 -0
- package/dist/tools/process.d.ts +3 -0
- package/dist/tools/process.js +56 -0
- package/dist/tools/prompts.d.ts +28 -0
- package/dist/tools/prompts.js +259 -0
- package/dist/tools/schemas.d.ts +454 -0
- package/dist/tools/schemas.js +242 -0
- package/dist/tools/usage.d.ts +5 -0
- package/dist/tools/usage.js +24 -0
- package/dist/types.d.ts +83 -0
- package/dist/types.js +1 -0
- package/dist/ui/config-editor/config-editor-runtime.js +150 -0
- package/dist/ui/config-editor/index.html +13 -0
- package/dist/ui/config-editor/src/app.d.ts +43 -0
- package/dist/ui/config-editor/src/app.js +831 -0
- package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
- package/dist/ui/config-editor/src/array-modal.js +185 -0
- package/dist/ui/config-editor/src/main.d.ts +1 -0
- package/dist/ui/config-editor/src/main.js +2 -0
- package/dist/ui/config-editor/styles.css +587 -0
- package/dist/ui/contracts.d.ts +14 -0
- package/dist/ui/contracts.js +21 -0
- package/dist/ui/file-preview/index.html +16 -0
- package/dist/ui/file-preview/preview-runtime.js +469 -0
- package/dist/ui/file-preview/shared/preview-file-types.d.ts +5 -0
- package/dist/ui/file-preview/shared/preview-file-types.js +59 -0
- package/dist/ui/file-preview/src/app.d.ts +4 -0
- package/dist/ui/file-preview/src/app.js +680 -0
- package/dist/ui/file-preview/src/components/code-viewer.d.ts +6 -0
- package/dist/ui/file-preview/src/components/code-viewer.js +73 -0
- package/dist/ui/file-preview/src/components/highlighting.d.ts +2 -0
- package/dist/ui/file-preview/src/components/highlighting.js +54 -0
- package/dist/ui/file-preview/src/components/html-renderer.d.ts +5 -0
- package/dist/ui/file-preview/src/components/html-renderer.js +47 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.d.ts +1 -0
- package/dist/ui/file-preview/src/components/markdown-renderer.js +59 -0
- package/dist/ui/file-preview/src/directory-controller.d.ts +8 -0
- package/dist/ui/file-preview/src/directory-controller.js +240 -0
- package/dist/ui/file-preview/src/document-layout.d.ts +20 -0
- package/dist/ui/file-preview/src/document-layout.js +109 -0
- package/dist/ui/file-preview/src/document-outline.d.ts +17 -0
- package/dist/ui/file-preview/src/document-outline.js +97 -0
- package/dist/ui/file-preview/src/document-workspace.d.ts +19 -0
- package/dist/ui/file-preview/src/document-workspace.js +33 -0
- package/dist/ui/file-preview/src/file-type-handlers.d.ts +10 -0
- package/dist/ui/file-preview/src/file-type-handlers.js +109 -0
- package/dist/ui/file-preview/src/host/external-actions.d.ts +8 -0
- package/dist/ui/file-preview/src/host/external-actions.js +55 -0
- package/dist/ui/file-preview/src/host/selection-context.d.ts +9 -0
- package/dist/ui/file-preview/src/host/selection-context.js +106 -0
- package/dist/ui/file-preview/src/image-preview.d.ts +3 -0
- package/dist/ui/file-preview/src/image-preview.js +21 -0
- package/dist/ui/file-preview/src/main.d.ts +1 -0
- package/dist/ui/file-preview/src/main.js +5 -0
- package/dist/ui/file-preview/src/markdown/conflict-dialog.d.ts +40 -0
- package/dist/ui/file-preview/src/markdown/conflict-dialog.js +163 -0
- package/dist/ui/file-preview/src/markdown/controller.d.ts +44 -0
- package/dist/ui/file-preview/src/markdown/controller.js +1043 -0
- package/dist/ui/file-preview/src/markdown/editor.d.ts +131 -0
- package/dist/ui/file-preview/src/markdown/editor.js +1479 -0
- package/dist/ui/file-preview/src/markdown/linking.d.ts +16 -0
- package/dist/ui/file-preview/src/markdown/linking.js +228 -0
- package/dist/ui/file-preview/src/markdown/outline.d.ts +2 -0
- package/dist/ui/file-preview/src/markdown/outline.js +16 -0
- package/dist/ui/file-preview/src/markdown/parser.d.ts +30 -0
- package/dist/ui/file-preview/src/markdown/parser.js +38 -0
- package/dist/ui/file-preview/src/markdown/preview.d.ts +1 -0
- package/dist/ui/file-preview/src/markdown/preview.js +20 -0
- package/dist/ui/file-preview/src/markdown/slugify.d.ts +3 -0
- package/dist/ui/file-preview/src/markdown/slugify.js +31 -0
- package/dist/ui/file-preview/src/markdown/utils.d.ts +1 -0
- package/dist/ui/file-preview/src/markdown/utils.js +15 -0
- package/dist/ui/file-preview/src/model.d.ts +35 -0
- package/dist/ui/file-preview/src/model.js +1 -0
- package/dist/ui/file-preview/src/panel-actions.d.ts +17 -0
- package/dist/ui/file-preview/src/panel-actions.js +182 -0
- package/dist/ui/file-preview/src/path-utils.d.ts +6 -0
- package/dist/ui/file-preview/src/path-utils.js +64 -0
- package/dist/ui/file-preview/src/payload-utils.d.ts +11 -0
- package/dist/ui/file-preview/src/payload-utils.js +119 -0
- package/dist/ui/file-preview/src/types.d.ts +1 -0
- package/dist/ui/file-preview/src/types.js +1 -0
- package/dist/ui/file-preview/styles.css +1631 -0
- package/dist/ui/resources.d.ts +28 -0
- package/dist/ui/resources.js +86 -0
- package/dist/ui/shared/compact-row.d.ts +11 -0
- package/dist/ui/shared/compact-row.js +18 -0
- package/dist/ui/shared/escape-html.d.ts +4 -0
- package/dist/ui/shared/escape-html.js +11 -0
- package/dist/ui/shared/host-context.d.ts +15 -0
- package/dist/ui/shared/host-context.js +51 -0
- package/dist/ui/shared/tool-bridge.d.ts +30 -0
- package/dist/ui/shared/tool-bridge.js +137 -0
- package/dist/ui/shared/tool-shell.d.ts +25 -0
- package/dist/ui/shared/tool-shell.js +107 -0
- package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
- package/dist/ui/shared/ui-event-tracker.js +27 -0
- package/dist/ui/shared/widget-state.d.ts +33 -0
- package/dist/ui/shared/widget-state.js +158 -0
- package/dist/utils/ab-test.d.ts +12 -0
- package/dist/utils/ab-test.js +104 -0
- package/dist/utils/capture.d.ts +35 -0
- package/dist/utils/capture.js +481 -0
- package/dist/utils/dockerPrompt.d.ts +17 -0
- package/dist/utils/dockerPrompt.js +53 -0
- package/dist/utils/feature-flags.d.ts +59 -0
- package/dist/utils/feature-flags.js +215 -0
- package/dist/utils/files/base.d.ts +173 -0
- package/dist/utils/files/base.js +5 -0
- package/dist/utils/files/binary.d.ts +21 -0
- package/dist/utils/files/binary.js +65 -0
- package/dist/utils/files/docx.d.ts +47 -0
- package/dist/utils/files/docx.js +679 -0
- package/dist/utils/files/excel.d.ts +24 -0
- package/dist/utils/files/excel.js +437 -0
- package/dist/utils/files/factory.d.ts +41 -0
- package/dist/utils/files/factory.js +113 -0
- package/dist/utils/files/image.d.ts +21 -0
- package/dist/utils/files/image.js +78 -0
- package/dist/utils/files/index.d.ts +10 -0
- package/dist/utils/files/index.js +13 -0
- package/dist/utils/files/pdf.d.ts +32 -0
- package/dist/utils/files/pdf.js +142 -0
- package/dist/utils/files/text.d.ts +63 -0
- package/dist/utils/files/text.js +370 -0
- package/dist/utils/fuzzySearchLogger.d.ts +30 -0
- package/dist/utils/fuzzySearchLogger.js +126 -0
- package/dist/utils/lineEndingHandler.d.ts +21 -0
- package/dist/utils/lineEndingHandler.js +77 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.js +72 -0
- package/dist/utils/mcp-ui-ab-test.d.ts +13 -0
- package/dist/utils/mcp-ui-ab-test.js +62 -0
- package/dist/utils/open-browser.d.ts +9 -0
- package/dist/utils/open-browser.js +42 -0
- package/dist/utils/process-detection.d.ts +23 -0
- package/dist/utils/process-detection.js +150 -0
- package/dist/utils/ripgrep-resolver.d.ts +9 -0
- package/dist/utils/ripgrep-resolver.js +74 -0
- package/dist/utils/system-info.d.ts +71 -0
- package/dist/utils/system-info.js +712 -0
- package/dist/utils/toolHistory.d.ts +86 -0
- package/dist/utils/toolHistory.js +257 -0
- package/dist/utils/trackTools.d.ts +6 -0
- package/dist/utils/trackTools.js +54 -0
- package/dist/utils/unsupportedParams.d.ts +24 -0
- package/dist/utils/unsupportedParams.js +66 -0
- package/dist/utils/usageTracker.d.ts +133 -0
- package/dist/utils/usageTracker.js +477 -0
- package/dist/utils/welcome-onboarding.d.ts +11 -0
- package/dist/utils/welcome-onboarding.js +99 -0
- package/dist/utils/withTimeout.d.ts +28 -0
- package/dist/utils/withTimeout.js +85 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +131 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { terminalManager, MAX_BUFFERED_OUTPUT_CHARS } from '../terminal-manager.js';
|
|
2
|
+
import { commandManager } from '../command-manager.js';
|
|
3
|
+
import { StartProcessArgsSchema, ReadProcessOutputArgsSchema, InteractWithProcessArgsSchema, ForceTerminateArgsSchema } from './schemas.js';
|
|
4
|
+
import { validatePath } from './filesystem.js';
|
|
5
|
+
import { capture } from "../utils/capture.js";
|
|
6
|
+
import { analyzeProcessState, cleanProcessOutput, formatProcessStateMessage } from '../utils/process-detection.js';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import { configManager } from '../config-manager.js';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import fs from 'fs/promises';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
// Get the directory where the MCP is installed (for ES module imports)
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
const mcpRoot = path.resolve(__dirname, '..', '..');
|
|
17
|
+
// Track virtual Node sessions (PIDs that are actually Node fallback sessions)
|
|
18
|
+
const virtualNodeSessions = new Map();
|
|
19
|
+
let virtualPidCounter = -1000; // Use negative PIDs for virtual sessions
|
|
20
|
+
/**
|
|
21
|
+
* Execute Node.js code via temp file (fallback when Python unavailable)
|
|
22
|
+
* Creates temp .mjs file in MCP directory for ES module import access
|
|
23
|
+
*/
|
|
24
|
+
async function executeNodeCode(code, timeout_ms = 30000) {
|
|
25
|
+
const tempFile = path.join(mcpRoot, `.mcp-exec-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
|
|
26
|
+
try {
|
|
27
|
+
await fs.writeFile(tempFile, code, 'utf8');
|
|
28
|
+
const result = await new Promise((resolve) => {
|
|
29
|
+
const proc = spawn(process.execPath, [tempFile], {
|
|
30
|
+
cwd: mcpRoot,
|
|
31
|
+
timeout: timeout_ms,
|
|
32
|
+
windowsHide: true // Prevent visible console windows on Windows
|
|
33
|
+
});
|
|
34
|
+
let stdout = '';
|
|
35
|
+
let stderr = '';
|
|
36
|
+
proc.stdout.on('data', (data) => {
|
|
37
|
+
stdout += data.toString();
|
|
38
|
+
});
|
|
39
|
+
proc.stderr.on('data', (data) => {
|
|
40
|
+
stderr += data.toString();
|
|
41
|
+
});
|
|
42
|
+
proc.on('close', (exitCode) => {
|
|
43
|
+
resolve({ stdout, stderr, exitCode: exitCode ?? 1 });
|
|
44
|
+
});
|
|
45
|
+
proc.on('error', (err) => {
|
|
46
|
+
resolve({ stdout, stderr: stderr + '\n' + err.message, exitCode: 1 });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
// Clean up temp file
|
|
50
|
+
await fs.unlink(tempFile).catch(() => { });
|
|
51
|
+
if (result.exitCode !== 0) {
|
|
52
|
+
return {
|
|
53
|
+
content: [{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: `Execution failed (exit code ${result.exitCode}):\n${result.stderr}\n${result.stdout}`
|
|
56
|
+
}],
|
|
57
|
+
isError: true
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
content: [{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: result.stdout || '(no output)'
|
|
64
|
+
}]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// Clean up temp file on error
|
|
69
|
+
await fs.unlink(tempFile).catch(() => { });
|
|
70
|
+
return {
|
|
71
|
+
content: [{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: `Failed to execute Node.js code: ${error instanceof Error ? error.message : String(error)}`
|
|
74
|
+
}],
|
|
75
|
+
isError: true
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Start a new process (renamed from execute_command)
|
|
81
|
+
* Includes early detection of process waiting for input
|
|
82
|
+
*/
|
|
83
|
+
export async function startProcess(args) {
|
|
84
|
+
const parsed = StartProcessArgsSchema.safeParse(args);
|
|
85
|
+
if (!parsed.success) {
|
|
86
|
+
capture('server_start_process_failed');
|
|
87
|
+
return {
|
|
88
|
+
content: [{ type: "text", text: `Error: Invalid arguments for start_process: ${parsed.error}` }],
|
|
89
|
+
isError: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const commands = commandManager.extractCommands(parsed.data.command).join(', ');
|
|
94
|
+
capture('server_start_process', {
|
|
95
|
+
command: commandManager.getBaseCommand(parsed.data.command),
|
|
96
|
+
commands: commands
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
capture('server_start_process', {
|
|
101
|
+
command: commandManager.getBaseCommand(parsed.data.command)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
const isAllowed = await commandManager.validateCommand(parsed.data.command);
|
|
105
|
+
if (!isAllowed) {
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text", text: `Error: Command not allowed: ${parsed.data.command}` }],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const commandToRun = parsed.data.command;
|
|
112
|
+
const workingDirectory = parsed.data.working_directory
|
|
113
|
+
? await validatePath(parsed.data.working_directory)
|
|
114
|
+
: undefined;
|
|
115
|
+
// Handle node:local - runs Node.js code directly on MCP server
|
|
116
|
+
if (commandToRun.trim() === 'node:local') {
|
|
117
|
+
const virtualPid = virtualPidCounter--;
|
|
118
|
+
virtualNodeSessions.set(virtualPid, { timeout_ms: parsed.data.timeout_ms || 30000 });
|
|
119
|
+
return {
|
|
120
|
+
content: [{
|
|
121
|
+
type: "text",
|
|
122
|
+
text: `Node.js session started with PID ${virtualPid} (MCP server execution)
|
|
123
|
+
|
|
124
|
+
IMPORTANT: Each interact_with_process call runs as a FRESH script.
|
|
125
|
+
State is NOT preserved between calls. Include ALL code in ONE call:
|
|
126
|
+
- imports, file reading, processing, and output together.
|
|
127
|
+
|
|
128
|
+
Available libraries:
|
|
129
|
+
- ExcelJS for Excel files: import ExcelJS from 'exceljs'
|
|
130
|
+
- All Node.js built-ins: fs, path, http, crypto, etc.
|
|
131
|
+
|
|
132
|
+
🔄 Ready for code - send complete self-contained script via interact_with_process.`
|
|
133
|
+
}],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
let shellUsed = parsed.data.shell;
|
|
137
|
+
if (!shellUsed) {
|
|
138
|
+
const config = await configManager.getConfig();
|
|
139
|
+
if (config.defaultShell) {
|
|
140
|
+
shellUsed = config.defaultShell;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const isWindows = os.platform() === 'win32';
|
|
144
|
+
if (isWindows && process.env.COMSPEC) {
|
|
145
|
+
shellUsed = process.env.COMSPEC;
|
|
146
|
+
}
|
|
147
|
+
else if (!isWindows && process.env.SHELL) {
|
|
148
|
+
shellUsed = process.env.SHELL;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
shellUsed = isWindows ? 'cmd.exe' : '/bin/sh';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const result = await terminalManager.executeCommand(commandToRun, parsed.data.timeout_ms, shellUsed, parsed.data.verbose_timing || false, workingDirectory);
|
|
156
|
+
if (result.pid === -1) {
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: "text", text: result.output }],
|
|
159
|
+
isError: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Analyze the process state to detect if it's waiting for input
|
|
163
|
+
const processState = analyzeProcessState(result.output, result.pid);
|
|
164
|
+
let statusMessage = '';
|
|
165
|
+
if (processState.isWaitingForInput) {
|
|
166
|
+
statusMessage = `\n🔄 ${formatProcessStateMessage(processState, result.pid)}`;
|
|
167
|
+
}
|
|
168
|
+
else if (processState.isFinished) {
|
|
169
|
+
statusMessage = `\n✅ ${formatProcessStateMessage(processState, result.pid)}`;
|
|
170
|
+
}
|
|
171
|
+
else if (result.isBlocked) {
|
|
172
|
+
statusMessage = '\n⏳ Process is running. Use read_process_output to get more output.';
|
|
173
|
+
}
|
|
174
|
+
// Add timing information if requested
|
|
175
|
+
let timingMessage = '';
|
|
176
|
+
if (result.timingInfo) {
|
|
177
|
+
timingMessage = formatTimingInfo(result.timingInfo);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
content: [{
|
|
181
|
+
type: "text",
|
|
182
|
+
text: `Process started with PID ${result.pid} (shell: ${shellUsed})\nInitial output:\n${result.output}${statusMessage}${timingMessage}`
|
|
183
|
+
}],
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function formatTimingInfo(timing) {
|
|
187
|
+
let msg = '\n\n📊 Timing Information:\n';
|
|
188
|
+
msg += ` Exit Reason: ${timing.exitReason}\n`;
|
|
189
|
+
msg += ` Total Duration: ${timing.totalDurationMs}ms\n`;
|
|
190
|
+
if (timing.timeToFirstOutputMs !== undefined) {
|
|
191
|
+
msg += ` Time to First Output: ${timing.timeToFirstOutputMs}ms\n`;
|
|
192
|
+
}
|
|
193
|
+
if (timing.firstOutputTime && timing.lastOutputTime) {
|
|
194
|
+
msg += ` Output Window: ${timing.lastOutputTime - timing.firstOutputTime}ms\n`;
|
|
195
|
+
}
|
|
196
|
+
if (timing.outputEvents && timing.outputEvents.length > 0) {
|
|
197
|
+
msg += `\n Output Events (${timing.outputEvents.length} total):\n`;
|
|
198
|
+
timing.outputEvents.forEach((event, idx) => {
|
|
199
|
+
msg += ` [${idx + 1}] +${event.deltaMs}ms | ${event.source} | ${event.length}b`;
|
|
200
|
+
if (event.matchedPattern) {
|
|
201
|
+
msg += ` | 🎯 ${event.matchedPattern}`;
|
|
202
|
+
}
|
|
203
|
+
msg += `\n "${event.snippet}"\n`;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return msg;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Read output from a running process with file-like pagination
|
|
210
|
+
* Supports offset/length parameters for controlled reading
|
|
211
|
+
*/
|
|
212
|
+
export async function readProcessOutput(args) {
|
|
213
|
+
const parsed = ReadProcessOutputArgsSchema.safeParse(args);
|
|
214
|
+
if (!parsed.success) {
|
|
215
|
+
return {
|
|
216
|
+
content: [{ type: "text", text: `Error: Invalid arguments for read_process_output: ${parsed.error}` }],
|
|
217
|
+
isError: true,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// Get default line limit from config
|
|
221
|
+
const config = await configManager.getConfig();
|
|
222
|
+
const defaultLength = config.fileReadLineLimit ?? 1000;
|
|
223
|
+
const { pid, timeout_ms = 5000, offset = 0, // 0 = from last read, positive = absolute, negative = tail
|
|
224
|
+
length = defaultLength, // Default from config, same as file reading
|
|
225
|
+
verbose_timing = false } = parsed.data;
|
|
226
|
+
// Timing telemetry
|
|
227
|
+
const startTime = Date.now();
|
|
228
|
+
// For active sessions with no new output yet, optionally wait for output
|
|
229
|
+
const session = terminalManager.getSession(pid);
|
|
230
|
+
if (session && offset === 0) {
|
|
231
|
+
// Wait for new output to arrive (only for "new output" reads, not absolute/tail)
|
|
232
|
+
const waitForOutput = () => {
|
|
233
|
+
return new Promise((resolve) => {
|
|
234
|
+
// Check if there's already new output
|
|
235
|
+
const currentLines = terminalManager.getOutputLineCount(pid) || 0;
|
|
236
|
+
if (currentLines > session.lastReadIndex) {
|
|
237
|
+
resolve();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
let resolved = false;
|
|
241
|
+
let interval = null;
|
|
242
|
+
let timeout = null;
|
|
243
|
+
const cleanup = () => {
|
|
244
|
+
if (interval)
|
|
245
|
+
clearInterval(interval);
|
|
246
|
+
if (timeout)
|
|
247
|
+
clearTimeout(timeout);
|
|
248
|
+
};
|
|
249
|
+
const resolveOnce = () => {
|
|
250
|
+
if (resolved)
|
|
251
|
+
return;
|
|
252
|
+
resolved = true;
|
|
253
|
+
cleanup();
|
|
254
|
+
resolve();
|
|
255
|
+
};
|
|
256
|
+
// Poll for new output
|
|
257
|
+
interval = setInterval(() => {
|
|
258
|
+
const newLineCount = terminalManager.getOutputLineCount(pid) || 0;
|
|
259
|
+
if (newLineCount > session.lastReadIndex) {
|
|
260
|
+
resolveOnce();
|
|
261
|
+
}
|
|
262
|
+
}, 50);
|
|
263
|
+
// Timeout
|
|
264
|
+
timeout = setTimeout(() => {
|
|
265
|
+
resolveOnce();
|
|
266
|
+
}, timeout_ms);
|
|
267
|
+
});
|
|
268
|
+
};
|
|
269
|
+
await waitForOutput();
|
|
270
|
+
}
|
|
271
|
+
// Read output with pagination
|
|
272
|
+
const result = terminalManager.readOutputPaginated(pid, offset, length);
|
|
273
|
+
if (!result) {
|
|
274
|
+
return {
|
|
275
|
+
content: [{ type: "text", text: `No session found for PID ${pid}` }],
|
|
276
|
+
isError: true,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// Join lines back into string
|
|
280
|
+
const output = result.lines.join('\n');
|
|
281
|
+
// Generate status message similar to file reading
|
|
282
|
+
let statusMessage = '';
|
|
283
|
+
if (offset < 0) {
|
|
284
|
+
// Tail read - match file reading format for consistency
|
|
285
|
+
statusMessage = `[Reading last ${result.readCount} lines (total: ${result.totalLines} lines)]`;
|
|
286
|
+
}
|
|
287
|
+
else if (offset === 0) {
|
|
288
|
+
// "New output" read
|
|
289
|
+
if (result.remaining > 0) {
|
|
290
|
+
statusMessage = `[Reading ${result.readCount} new lines from line ${result.readFrom} (total: ${result.totalLines} lines, ${result.remaining} remaining)]`;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
statusMessage = `[Reading ${result.readCount} new lines (total: ${result.totalLines} lines)]`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
// Absolute position read
|
|
298
|
+
statusMessage = `[Reading ${result.readCount} lines from line ${result.readFrom} (total: ${result.totalLines} lines, ${result.remaining} remaining)]`;
|
|
299
|
+
}
|
|
300
|
+
// Surface buffer-cap eviction so the model knows the retained output is not
|
|
301
|
+
// the full output and that line numbers shifted (matches the truncation
|
|
302
|
+
// markers used by other tools).
|
|
303
|
+
if (result.evictedLines && result.evictedLines > 0) {
|
|
304
|
+
const capMB = Math.round(MAX_BUFFERED_OUTPUT_CHARS / 1024 / 1024);
|
|
305
|
+
statusMessage += `\n[WARNING: output exceeded the ${capMB}MB buffer cap; the ${result.evictedLines} earliest lines were evicted and cannot be read. Line numbers and totals refer to the retained buffer only]`;
|
|
306
|
+
}
|
|
307
|
+
// Add process state info
|
|
308
|
+
let processStateMessage = '';
|
|
309
|
+
if (result.isComplete) {
|
|
310
|
+
const runtimeStr = result.runtimeMs !== undefined
|
|
311
|
+
? ` (runtime: ${(result.runtimeMs / 1000).toFixed(2)}s)`
|
|
312
|
+
: '';
|
|
313
|
+
processStateMessage = `\n✅ Process completed with exit code ${result.exitCode}${runtimeStr}`;
|
|
314
|
+
}
|
|
315
|
+
else if (session) {
|
|
316
|
+
// Analyze state for running processes
|
|
317
|
+
const fullOutput = session.outputLines.join('\n');
|
|
318
|
+
const processState = analyzeProcessState(fullOutput, pid);
|
|
319
|
+
if (processState.isWaitingForInput) {
|
|
320
|
+
processStateMessage = `\n🔄 ${formatProcessStateMessage(processState, pid)}`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Add timing information if requested
|
|
324
|
+
let timingMessage = '';
|
|
325
|
+
if (verbose_timing) {
|
|
326
|
+
const endTime = Date.now();
|
|
327
|
+
timingMessage = `\n\n📊 Timing: ${endTime - startTime}ms`;
|
|
328
|
+
}
|
|
329
|
+
const responseText = output || '(No output in requested range)';
|
|
330
|
+
return {
|
|
331
|
+
content: [{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: `${statusMessage}\n\n${responseText}${processStateMessage}${timingMessage}`
|
|
334
|
+
}],
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Interact with a running process (renamed from send_input)
|
|
339
|
+
* Automatically detects when process is ready and returns output
|
|
340
|
+
*/
|
|
341
|
+
export async function interactWithProcess(args) {
|
|
342
|
+
const parsed = InteractWithProcessArgsSchema.safeParse(args);
|
|
343
|
+
if (!parsed.success) {
|
|
344
|
+
capture('server_interact_with_process_failed', {
|
|
345
|
+
error: 'Invalid arguments'
|
|
346
|
+
});
|
|
347
|
+
return {
|
|
348
|
+
content: [{ type: "text", text: `Error: Invalid arguments for interact_with_process: ${parsed.error}` }],
|
|
349
|
+
isError: true,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const { pid, input, timeout_ms = 8000, wait_for_prompt = true, verbose_timing = false } = parsed.data;
|
|
353
|
+
// Get config for output line limit
|
|
354
|
+
const config = await configManager.getConfig();
|
|
355
|
+
const maxOutputLines = config.fileReadLineLimit ?? 1000;
|
|
356
|
+
// Check if this is a virtual Node session (node:local)
|
|
357
|
+
if (virtualNodeSessions.has(pid)) {
|
|
358
|
+
const session = virtualNodeSessions.get(pid);
|
|
359
|
+
capture('server_interact_with_process_node_fallback', {
|
|
360
|
+
pid: pid,
|
|
361
|
+
inputLength: input.length
|
|
362
|
+
});
|
|
363
|
+
// Execute code via temp file approach
|
|
364
|
+
// Respect per-call timeout if provided, otherwise use session default
|
|
365
|
+
const effectiveTimeout = timeout_ms ?? session.timeout_ms;
|
|
366
|
+
return executeNodeCode(input, effectiveTimeout);
|
|
367
|
+
}
|
|
368
|
+
// Timing telemetry
|
|
369
|
+
const startTime = Date.now();
|
|
370
|
+
let firstOutputTime;
|
|
371
|
+
let lastOutputTime;
|
|
372
|
+
const outputEvents = [];
|
|
373
|
+
let exitReason = 'timeout';
|
|
374
|
+
try {
|
|
375
|
+
capture('server_interact_with_process', {
|
|
376
|
+
pid: pid,
|
|
377
|
+
inputLength: input.length
|
|
378
|
+
});
|
|
379
|
+
// Capture output snapshot BEFORE sending input
|
|
380
|
+
// This handles REPLs where output is appended to the prompt line
|
|
381
|
+
const outputSnapshot = terminalManager.captureOutputSnapshot(pid);
|
|
382
|
+
const success = terminalManager.sendInputToProcess(pid, input);
|
|
383
|
+
if (!success) {
|
|
384
|
+
return {
|
|
385
|
+
content: [{ type: "text", text: `Error: Failed to send input to process ${pid}. The process may have exited or doesn't accept input.` }],
|
|
386
|
+
isError: true,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
// If not waiting for response, return immediately
|
|
390
|
+
if (!wait_for_prompt) {
|
|
391
|
+
exitReason = 'no_wait';
|
|
392
|
+
let timingMessage = '';
|
|
393
|
+
if (verbose_timing) {
|
|
394
|
+
const endTime = Date.now();
|
|
395
|
+
const timingInfo = {
|
|
396
|
+
startTime,
|
|
397
|
+
endTime,
|
|
398
|
+
totalDurationMs: endTime - startTime,
|
|
399
|
+
exitReason,
|
|
400
|
+
firstOutputTime,
|
|
401
|
+
lastOutputTime,
|
|
402
|
+
timeToFirstOutputMs: undefined,
|
|
403
|
+
outputEvents: undefined
|
|
404
|
+
};
|
|
405
|
+
timingMessage = formatTimingInfo(timingInfo);
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
content: [{
|
|
409
|
+
type: "text",
|
|
410
|
+
text: `✅ Input sent to process ${pid}. Use read_process_output to get the response.${timingMessage}`
|
|
411
|
+
}],
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// Smart waiting with immediate and periodic detection
|
|
415
|
+
let output = "";
|
|
416
|
+
let processState;
|
|
417
|
+
let earlyExit = false;
|
|
418
|
+
// Quick prompt patterns for immediate detection
|
|
419
|
+
const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/;
|
|
420
|
+
const waitForResponse = () => {
|
|
421
|
+
return new Promise((resolve) => {
|
|
422
|
+
let resolved = false;
|
|
423
|
+
let attempts = 0;
|
|
424
|
+
const pollIntervalMs = 50; // Poll every 50ms for faster response
|
|
425
|
+
const maxAttempts = Math.ceil(timeout_ms / pollIntervalMs);
|
|
426
|
+
let interval = null;
|
|
427
|
+
let lastOutputLength = 0; // Track output length to detect new output
|
|
428
|
+
let resolveOnce = () => {
|
|
429
|
+
if (resolved)
|
|
430
|
+
return;
|
|
431
|
+
resolved = true;
|
|
432
|
+
if (interval)
|
|
433
|
+
clearInterval(interval);
|
|
434
|
+
resolve();
|
|
435
|
+
};
|
|
436
|
+
// Fast-polling check - check every 50ms for quick responses
|
|
437
|
+
interval = setInterval(() => {
|
|
438
|
+
if (resolved)
|
|
439
|
+
return;
|
|
440
|
+
// Use snapshot-based reading to handle REPL prompt line appending
|
|
441
|
+
const newOutput = outputSnapshot
|
|
442
|
+
? terminalManager.getOutputSinceSnapshot(pid, outputSnapshot)
|
|
443
|
+
: terminalManager.getNewOutput(pid);
|
|
444
|
+
if (newOutput && newOutput.length > lastOutputLength) {
|
|
445
|
+
const now = Date.now();
|
|
446
|
+
if (!firstOutputTime)
|
|
447
|
+
firstOutputTime = now;
|
|
448
|
+
lastOutputTime = now;
|
|
449
|
+
if (verbose_timing) {
|
|
450
|
+
outputEvents.push({
|
|
451
|
+
timestamp: now,
|
|
452
|
+
deltaMs: now - startTime,
|
|
453
|
+
source: 'periodic_poll',
|
|
454
|
+
length: newOutput.length - lastOutputLength,
|
|
455
|
+
snippet: newOutput.slice(lastOutputLength, lastOutputLength + 50).replace(/\n/g, '\\n')
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
output = newOutput; // Replace with full output since snapshot
|
|
459
|
+
lastOutputLength = newOutput.length;
|
|
460
|
+
// Analyze current state
|
|
461
|
+
processState = analyzeProcessState(output, pid);
|
|
462
|
+
// Exit early if we detect the process is waiting for input
|
|
463
|
+
if (processState.isWaitingForInput) {
|
|
464
|
+
earlyExit = true;
|
|
465
|
+
exitReason = 'early_exit_periodic_check';
|
|
466
|
+
if (verbose_timing && outputEvents.length > 0) {
|
|
467
|
+
outputEvents[outputEvents.length - 1].matchedPattern = 'periodic_check';
|
|
468
|
+
}
|
|
469
|
+
resolveOnce();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
// Also exit if process finished
|
|
473
|
+
if (processState.isFinished) {
|
|
474
|
+
exitReason = 'process_finished';
|
|
475
|
+
resolveOnce();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
attempts++;
|
|
480
|
+
if (attempts >= maxAttempts) {
|
|
481
|
+
exitReason = 'timeout';
|
|
482
|
+
resolveOnce();
|
|
483
|
+
}
|
|
484
|
+
}, pollIntervalMs);
|
|
485
|
+
});
|
|
486
|
+
};
|
|
487
|
+
await waitForResponse();
|
|
488
|
+
// Clean and format output
|
|
489
|
+
let cleanOutput = cleanProcessOutput(output, input);
|
|
490
|
+
const timeoutReached = !earlyExit && !processState?.isFinished && !processState?.isWaitingForInput;
|
|
491
|
+
// Apply output line limit to prevent context overflow
|
|
492
|
+
let truncationMessage = '';
|
|
493
|
+
const outputLines = cleanOutput.split('\n');
|
|
494
|
+
if (outputLines.length > maxOutputLines) {
|
|
495
|
+
const truncatedLines = outputLines.slice(0, maxOutputLines);
|
|
496
|
+
cleanOutput = truncatedLines.join('\n');
|
|
497
|
+
const remainingLines = outputLines.length - maxOutputLines;
|
|
498
|
+
truncationMessage = `\n\n⚠️ Output truncated: showing ${maxOutputLines} of ${outputLines.length} lines (${remainingLines} hidden). Use read_process_output with offset/length for full output.`;
|
|
499
|
+
}
|
|
500
|
+
// Determine final state
|
|
501
|
+
if (!processState) {
|
|
502
|
+
processState = analyzeProcessState(output, pid);
|
|
503
|
+
}
|
|
504
|
+
let statusMessage = '';
|
|
505
|
+
if (processState.isWaitingForInput) {
|
|
506
|
+
statusMessage = `\n🔄 ${formatProcessStateMessage(processState, pid)}`;
|
|
507
|
+
}
|
|
508
|
+
else if (processState.isFinished) {
|
|
509
|
+
statusMessage = `\n✅ ${formatProcessStateMessage(processState, pid)}`;
|
|
510
|
+
}
|
|
511
|
+
else if (timeoutReached) {
|
|
512
|
+
statusMessage = '\n⏱️ Response may be incomplete (timeout reached)';
|
|
513
|
+
}
|
|
514
|
+
// Add timing information if requested
|
|
515
|
+
let timingMessage = '';
|
|
516
|
+
if (verbose_timing) {
|
|
517
|
+
const endTime = Date.now();
|
|
518
|
+
const timingInfo = {
|
|
519
|
+
startTime,
|
|
520
|
+
endTime,
|
|
521
|
+
totalDurationMs: endTime - startTime,
|
|
522
|
+
exitReason,
|
|
523
|
+
firstOutputTime,
|
|
524
|
+
lastOutputTime,
|
|
525
|
+
timeToFirstOutputMs: firstOutputTime ? firstOutputTime - startTime : undefined,
|
|
526
|
+
outputEvents: outputEvents.length > 0 ? outputEvents : undefined
|
|
527
|
+
};
|
|
528
|
+
timingMessage = formatTimingInfo(timingInfo);
|
|
529
|
+
}
|
|
530
|
+
if (cleanOutput.trim().length === 0 && !timeoutReached) {
|
|
531
|
+
return {
|
|
532
|
+
content: [{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: `✅ Input executed in process ${pid}.\n📭 (No output produced)${statusMessage}${timingMessage}`
|
|
535
|
+
}],
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
// Format response with better structure and consistent emojis
|
|
539
|
+
let responseText = `✅ Input executed in process ${pid}`;
|
|
540
|
+
if (cleanOutput && cleanOutput.trim().length > 0) {
|
|
541
|
+
responseText += `:\n\n📤 Output:\n${cleanOutput}`;
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
responseText += `.\n📭 (No output produced)`;
|
|
545
|
+
}
|
|
546
|
+
if (statusMessage) {
|
|
547
|
+
responseText += `\n\n${statusMessage}`;
|
|
548
|
+
}
|
|
549
|
+
if (truncationMessage) {
|
|
550
|
+
responseText += truncationMessage;
|
|
551
|
+
}
|
|
552
|
+
if (timingMessage) {
|
|
553
|
+
responseText += timingMessage;
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
content: [{
|
|
557
|
+
type: "text",
|
|
558
|
+
text: responseText
|
|
559
|
+
}],
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
564
|
+
capture('server_interact_with_process_error', {
|
|
565
|
+
error: errorMessage
|
|
566
|
+
});
|
|
567
|
+
return {
|
|
568
|
+
content: [{ type: "text", text: `Error interacting with process: ${errorMessage}` }],
|
|
569
|
+
isError: true,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Force terminate a process
|
|
575
|
+
*/
|
|
576
|
+
export async function forceTerminate(args) {
|
|
577
|
+
const parsed = ForceTerminateArgsSchema.safeParse(args);
|
|
578
|
+
if (!parsed.success) {
|
|
579
|
+
return {
|
|
580
|
+
content: [{ type: "text", text: `Error: Invalid arguments for force_terminate: ${parsed.error}` }],
|
|
581
|
+
isError: true,
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
const pid = parsed.data.pid;
|
|
585
|
+
// Handle virtual Node.js sessions (node:local)
|
|
586
|
+
if (virtualNodeSessions.has(pid)) {
|
|
587
|
+
virtualNodeSessions.delete(pid);
|
|
588
|
+
return {
|
|
589
|
+
content: [{
|
|
590
|
+
type: "text",
|
|
591
|
+
text: `Cleared virtual Node.js session ${pid}`
|
|
592
|
+
}],
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const success = terminalManager.forceTerminate(pid);
|
|
596
|
+
return {
|
|
597
|
+
content: [{
|
|
598
|
+
type: "text",
|
|
599
|
+
text: success
|
|
600
|
+
? `Successfully initiated termination of session ${pid}`
|
|
601
|
+
: `No active session found for PID ${pid}`
|
|
602
|
+
}],
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* List active sessions
|
|
607
|
+
*/
|
|
608
|
+
export async function listSessions() {
|
|
609
|
+
const sessions = terminalManager.listActiveSessions();
|
|
610
|
+
// Include virtual Node.js sessions
|
|
611
|
+
const virtualSessions = Array.from(virtualNodeSessions.entries()).map(([pid, session]) => ({
|
|
612
|
+
pid,
|
|
613
|
+
type: 'node:local',
|
|
614
|
+
timeout_ms: session.timeout_ms
|
|
615
|
+
}));
|
|
616
|
+
const realSessionsText = sessions.map(s => `PID: ${s.pid}, Blocked: ${s.isBlocked}, Runtime: ${Math.round(s.runtime / 1000)}s`);
|
|
617
|
+
const virtualSessionsText = virtualSessions.map(s => `PID: ${s.pid} (node:local), Timeout: ${s.timeout_ms}ms`);
|
|
618
|
+
const allSessions = [...realSessionsText, ...virtualSessionsText];
|
|
619
|
+
return {
|
|
620
|
+
content: [{
|
|
621
|
+
type: "text",
|
|
622
|
+
text: allSessions.length === 0
|
|
623
|
+
? 'No active sessions'
|
|
624
|
+
: allSessions.join('\n')
|
|
625
|
+
}],
|
|
626
|
+
};
|
|
627
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Simple MIME type detection based on file extension
|
|
2
|
+
export function getMimeType(filePath) {
|
|
3
|
+
const extension = filePath.toLowerCase().split('.').pop() || '';
|
|
4
|
+
if (extension === "pdf") {
|
|
5
|
+
return "application/pdf";
|
|
6
|
+
}
|
|
7
|
+
// Image types - only the formats we can display
|
|
8
|
+
const imageTypes = {
|
|
9
|
+
'png': 'image/png',
|
|
10
|
+
'jpg': 'image/jpeg',
|
|
11
|
+
'jpeg': 'image/jpeg',
|
|
12
|
+
'gif': 'image/gif',
|
|
13
|
+
'webp': 'image/webp'
|
|
14
|
+
};
|
|
15
|
+
// Check if the file is an image
|
|
16
|
+
if (extension in imageTypes) {
|
|
17
|
+
return imageTypes[extension];
|
|
18
|
+
}
|
|
19
|
+
// Default to text/plain for all other files
|
|
20
|
+
return 'text/plain';
|
|
21
|
+
}
|
|
22
|
+
export function isPdfFile(mimeType) {
|
|
23
|
+
const [baseType] = mimeType.toLowerCase().split(';');
|
|
24
|
+
return baseType.trim() === 'application/pdf';
|
|
25
|
+
}
|
|
26
|
+
export function isImageFile(mimeType) {
|
|
27
|
+
return mimeType.startsWith('image/');
|
|
28
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface ImageInfo {
|
|
2
|
+
/** Object ID within PDF */
|
|
3
|
+
objId: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
/** Raw image data as base64 */
|
|
7
|
+
data: string;
|
|
8
|
+
/** MIME type of the image */
|
|
9
|
+
mimeType: string;
|
|
10
|
+
/** Original size in bytes before compression */
|
|
11
|
+
originalSize?: number;
|
|
12
|
+
/** Compressed size in bytes */
|
|
13
|
+
compressedSize?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface PageImages {
|
|
16
|
+
pageNumber: number;
|
|
17
|
+
images: ImageInfo[];
|
|
18
|
+
}
|
|
19
|
+
export interface ImageCompressionOptions {
|
|
20
|
+
/** Output format: 'jpeg' | 'webp' */
|
|
21
|
+
format?: 'jpeg' | 'webp';
|
|
22
|
+
/** Quality for lossy formats (0-100, default 85) */
|
|
23
|
+
quality?: number;
|
|
24
|
+
/** Maximum dimension to resize to (maintains aspect ratio) */
|
|
25
|
+
maxDimension?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Optimized image extraction from PDF using unpdf's built-in extractImages method
|
|
29
|
+
* @param pdfBuffer PDF file as Uint8Array
|
|
30
|
+
* @param pageNumbers Optional array of specific page numbers to process
|
|
31
|
+
* @param compressionOptions Image compression settings
|
|
32
|
+
* @returns Record of page numbers to extracted images
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractImagesFromPdf(pdfBuffer: Uint8Array, pageNumbers?: number[], compressionOptions?: ImageCompressionOptions): Promise<Record<number, ImageInfo[]>>;
|