@mako10k/shell-server 0.2.3 → 0.3.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.
Files changed (95) hide show
  1. package/README.md +83 -11
  2. package/dist/backoffice/index.js +1 -1
  3. package/dist/backoffice/index.js.map +1 -1
  4. package/dist/backoffice/server.js +9 -9
  5. package/dist/backoffice/server.js.map +1 -1
  6. package/dist/cli.js +25 -4
  7. package/dist/cli.js.map +1 -1
  8. package/dist/core/file-manager.d.ts +3 -3
  9. package/dist/core/file-manager.js +27 -27
  10. package/dist/core/file-manager.js.map +1 -1
  11. package/dist/core/file-storage-subscriber.d.ts +7 -7
  12. package/dist/core/file-storage-subscriber.js +19 -19
  13. package/dist/core/file-storage-subscriber.js.map +1 -1
  14. package/dist/core/monitoring-manager.d.ts +1 -1
  15. package/dist/core/monitoring-manager.js +31 -31
  16. package/dist/core/monitoring-manager.js.map +1 -1
  17. package/dist/core/process-manager.d.ts +8 -5
  18. package/dist/core/process-manager.d.ts.map +1 -1
  19. package/dist/core/process-manager.js +427 -314
  20. package/dist/core/process-manager.js.map +1 -1
  21. package/dist/core/realtime-stream-subscriber.d.ts +18 -18
  22. package/dist/core/realtime-stream-subscriber.d.ts.map +1 -1
  23. package/dist/core/realtime-stream-subscriber.js +23 -23
  24. package/dist/core/realtime-stream-subscriber.js.map +1 -1
  25. package/dist/core/server-manager.d.ts +1 -0
  26. package/dist/core/server-manager.d.ts.map +1 -1
  27. package/dist/core/server-manager.js +26 -0
  28. package/dist/core/server-manager.js.map +1 -1
  29. package/dist/core/stream-publisher.d.ts +20 -20
  30. package/dist/core/stream-publisher.d.ts.map +1 -1
  31. package/dist/core/stream-publisher.js +15 -15
  32. package/dist/core/stream-publisher.js.map +1 -1
  33. package/dist/core/streaming-pipeline-reader.d.ts +12 -16
  34. package/dist/core/streaming-pipeline-reader.d.ts.map +1 -1
  35. package/dist/core/streaming-pipeline-reader.js +28 -28
  36. package/dist/core/streaming-pipeline-reader.js.map +1 -1
  37. package/dist/core/terminal-manager.d.ts +3 -3
  38. package/dist/core/terminal-manager.js +61 -61
  39. package/dist/core/terminal-manager.js.map +1 -1
  40. package/dist/daemon/server.d.ts.map +1 -1
  41. package/dist/daemon/server.js +165 -1
  42. package/dist/daemon/server.js.map +1 -1
  43. package/dist/executor/server.js +3 -3
  44. package/dist/executor/server.js.map +1 -1
  45. package/dist/runtime/tool-runtime.d.ts.map +1 -1
  46. package/dist/runtime/tool-runtime.js +9 -2
  47. package/dist/runtime/tool-runtime.js.map +1 -1
  48. package/dist/security/chat-completion-adapter.d.ts +2 -0
  49. package/dist/security/chat-completion-adapter.d.ts.map +1 -1
  50. package/dist/security/chat-completion-adapter.js +124 -7
  51. package/dist/security/chat-completion-adapter.js.map +1 -1
  52. package/dist/security/enhanced-evaluator.d.ts +2 -0
  53. package/dist/security/enhanced-evaluator.d.ts.map +1 -1
  54. package/dist/security/enhanced-evaluator.js +180 -11
  55. package/dist/security/enhanced-evaluator.js.map +1 -1
  56. package/dist/security/evaluator-types.d.ts +2 -2
  57. package/dist/security/kfence-fastpath.d.ts +15 -0
  58. package/dist/security/kfence-fastpath.d.ts.map +1 -0
  59. package/dist/security/kfence-fastpath.js +20 -0
  60. package/dist/security/kfence-fastpath.js.map +1 -0
  61. package/dist/security/manager.d.ts +4 -0
  62. package/dist/security/manager.d.ts.map +1 -1
  63. package/dist/security/manager.js +8 -2
  64. package/dist/security/manager.js.map +1 -1
  65. package/dist/security/security-llm-prompt-generator.d.ts +2 -2
  66. package/dist/security/security-llm-prompt-generator.js +5 -5
  67. package/dist/security/security-llm-prompt-generator.js.map +1 -1
  68. package/dist/tools/shell-tools.d.ts.map +1 -1
  69. package/dist/tools/shell-tools.js +59 -41
  70. package/dist/tools/shell-tools.js.map +1 -1
  71. package/dist/types/enhanced-security.d.ts +6 -6
  72. package/dist/types/enhanced-security.js +29 -29
  73. package/dist/types/enhanced-security.js.map +1 -1
  74. package/dist/types/index.d.ts +18 -5
  75. package/dist/types/index.d.ts.map +1 -1
  76. package/dist/types/index.js +37 -34
  77. package/dist/types/index.js.map +1 -1
  78. package/dist/types/quick-schemas.js +5 -5
  79. package/dist/types/quick-schemas.js.map +1 -1
  80. package/dist/types/response-schemas.js +4 -4
  81. package/dist/types/response-schemas.js.map +1 -1
  82. package/dist/types/schemas.d.ts +39 -58
  83. package/dist/types/schemas.d.ts.map +1 -1
  84. package/dist/types/schemas.js +25 -30
  85. package/dist/types/schemas.js.map +1 -1
  86. package/dist/utils/errors.d.ts +1 -1
  87. package/dist/utils/errors.d.ts.map +1 -1
  88. package/dist/utils/errors.js +3 -3
  89. package/dist/utils/errors.js.map +1 -1
  90. package/dist/utils/helpers.js +51 -51
  91. package/dist/utils/helpers.js.map +1 -1
  92. package/dist/utils/process-utils.d.ts +6 -6
  93. package/dist/utils/process-utils.js +29 -29
  94. package/dist/utils/process-utils.js.map +1 -1
  95. package/package.json +1 -1
@@ -12,63 +12,123 @@ export class ProcessManager {
12
12
  processes = new Map();
13
13
  maxConcurrentProcesses;
14
14
  outputDir;
15
- terminalManager; // TerminalManager への参照
16
- fileManager; // FileManager への参照
15
+ terminalManager; // Reference to TerminalManager
16
+ fileManager; // Reference to FileManager
17
17
  defaultWorkingDirectory;
18
18
  allowedWorkingDirectories;
19
- backgroundProcessCallbacks = {}; // バックグラウンドプロセス終了コールバック
20
- // Issue #13: PUB/SUB統合 - Feature Flag付きで段階的統合
19
+ backgroundProcessCallbacks = {}; // Background process completion callbacks
20
+ // Issue #13: PUB/SUB integration - phased rollout with feature flag
21
21
  streamPublisher;
22
22
  fileStorageSubscriber;
23
23
  realtimeStreamSubscriber;
24
24
  enableStreaming = false; // Feature Flag
25
- constructor(maxConcurrentProcesses = 50, outputDir = '/tmp/mcp-shell-outputs', fileManager) {
26
- this.maxConcurrentProcesses = maxConcurrentProcesses;
25
+ buildConcurrencyStopCandidates(limit = 5) {
26
+ const now = Date.now();
27
+ return Array.from(this.executions.values())
28
+ .filter((exec) => exec.status === 'running')
29
+ .sort((a, b) => {
30
+ const aStart = a.started_at ? new Date(a.started_at).getTime() : Number.MAX_SAFE_INTEGER;
31
+ const bStart = b.started_at ? new Date(b.started_at).getTime() : Number.MAX_SAFE_INTEGER;
32
+ return aStart - bStart;
33
+ })
34
+ .slice(0, limit)
35
+ .map((exec) => {
36
+ const startedAtMs = exec.started_at ? new Date(exec.started_at).getTime() : undefined;
37
+ const runtimeSeconds = startedAtMs && Number.isFinite(startedAtMs)
38
+ ? Math.max(0, Math.floor((now - startedAtMs) / 1000))
39
+ : undefined;
40
+ return {
41
+ execution_id: exec.execution_id,
42
+ ...(exec.process_id !== undefined ? { process_id: exec.process_id } : {}),
43
+ command: exec.command.length > 120 ? `${exec.command.slice(0, 117)}...` : exec.command,
44
+ ...(runtimeSeconds !== undefined ? { runtime_seconds: runtimeSeconds } : {}),
45
+ status: exec.status,
46
+ suggested_action: {
47
+ tool: 'process_kill',
48
+ parameters: {
49
+ ...(exec.process_id !== undefined ? { process_id: exec.process_id } : {}),
50
+ signal: 'TERM',
51
+ force: false,
52
+ },
53
+ },
54
+ };
55
+ });
56
+ }
57
+ getRunningExecutionCount() {
58
+ return Array.from(this.executions.values()).filter((exec) => exec.status === 'running').length;
59
+ }
60
+ async waitForExecutionSlot(maxWaitMs, pollIntervalMs = 100) {
61
+ if (this.getRunningExecutionCount() < this.maxConcurrentProcesses) {
62
+ return true;
63
+ }
64
+ if (maxWaitMs <= 0) {
65
+ return false;
66
+ }
67
+ const deadline = Date.now() + maxWaitMs;
68
+ while (Date.now() < deadline) {
69
+ await new Promise((resolve) => {
70
+ setTimeout(resolve, Math.min(pollIntervalMs, Math.max(1, deadline - Date.now())));
71
+ });
72
+ if (this.getRunningExecutionCount() < this.maxConcurrentProcesses) {
73
+ return true;
74
+ }
75
+ }
76
+ return this.getRunningExecutionCount() < this.maxConcurrentProcesses;
77
+ }
78
+ constructor(maxConcurrentProcesses = 8, outputDir = '/tmp/mcp-shell-outputs', fileManager) {
79
+ const envMaxConcurrentRaw = process.env['SHELL_SERVER_MAX_CONCURRENT_PROCESSES'];
80
+ const envMaxConcurrentParsed = envMaxConcurrentRaw
81
+ ? Number.parseInt(envMaxConcurrentRaw, 10)
82
+ : NaN;
83
+ const resolvedMaxConcurrent = Number.isFinite(envMaxConcurrentParsed) && envMaxConcurrentParsed > 0
84
+ ? envMaxConcurrentParsed
85
+ : maxConcurrentProcesses;
86
+ this.maxConcurrentProcesses = resolvedMaxConcurrent;
27
87
  this.outputDir = outputDir;
28
88
  this.fileManager = fileManager;
29
89
  this.defaultWorkingDirectory = process.env['SHELL_SERVER_DEFAULT_WORKDIR'] || process.cwd();
30
90
  this.allowedWorkingDirectories = process.env['SHELL_SERVER_ALLOWED_WORKDIRS']
31
91
  ? process.env['SHELL_SERVER_ALLOWED_WORKDIRS'].split(',').map((dir) => dir.trim())
32
92
  : [process.cwd()];
33
- // StreamPublisher初期化
93
+ // Initialize StreamPublisher
34
94
  this.streamPublisher = new StreamPublisher({
35
- enableRealtimeStreaming: false, // 初期状態は無効
95
+ enableRealtimeStreaming: false, // disabled by default
36
96
  bufferSize: 8192,
37
97
  notificationInterval: 100,
38
98
  });
39
- // 環境変数でStreaming機能を制御(段階的展開、デフォルト有効)
99
+ // Control streaming via environment variable (phased rollout, enabled by default)
40
100
  this.enableStreaming = process.env['SHELL_SERVER_ENABLE_STREAMING'] !== 'false';
41
101
  if (this.enableStreaming) {
42
102
  this.initializeStreamingComponents();
43
103
  }
44
104
  this.initializeOutputDirectory();
45
105
  }
46
- // TerminalManager への参照を設定
106
+ // Set TerminalManager reference
47
107
  setTerminalManager(terminalManager) {
48
108
  this.terminalManager = terminalManager;
49
109
  }
50
- // FileManager への参照を設定
110
+ // Set FileManager reference
51
111
  setFileManager(fileManager) {
52
112
  this.fileManager = fileManager;
53
- // FileManagerが設定された時にStreaming機能を再初期化
113
+ // Reinitialize streaming when FileManager is set
54
114
  if (this.enableStreaming) {
55
115
  this.initializeStreamingComponents();
56
116
  }
57
117
  }
58
- // バックグラウンドプロセス終了時のコールバックを設定
118
+ // Set callbacks for background process completion
59
119
  setBackgroundProcessCallbacks(callbacks) {
60
120
  this.backgroundProcessCallbacks = callbacks;
61
121
  }
62
- // Issue #13: Streaming コンポーネントの初期化
122
+ // Issue #13: Initialize streaming components
63
123
  initializeStreamingComponents() {
64
124
  if (!this.fileManager) {
65
125
  console.error('ProcessManager: FileManager is required for streaming components');
66
126
  return;
67
127
  }
68
- // FileStorageSubscriber初期化(既存FileManager機能を代替)
128
+ // Initialize FileStorageSubscriber (replacing part of existing FileManager handling)
69
129
  this.fileStorageSubscriber = new FileStorageSubscriber(this.fileManager, this.outputDir);
70
130
  this.streamPublisher.subscribe(this.fileStorageSubscriber);
71
- // RealtimeStreamSubscriber初期化
131
+ // Initialize RealtimeStreamSubscriber
72
132
  this.realtimeStreamSubscriber = new RealtimeStreamSubscriber({
73
133
  bufferSize: 8192,
74
134
  notificationInterval: 100,
@@ -78,14 +138,14 @@ export class ProcessManager {
78
138
  this.streamPublisher.subscribe(this.realtimeStreamSubscriber);
79
139
  console.error('ProcessManager: Streaming components initialized');
80
140
  }
81
- // Issue #13: Streaming機能の有効/無効切り替え
141
+ // Issue #13: Enable/disable streaming
82
142
  enableStreamingFeature(enable = true) {
83
143
  this.enableStreaming = enable;
84
144
  if (enable && this.fileManager) {
85
145
  this.initializeStreamingComponents();
86
146
  }
87
147
  else if (!enable) {
88
- // Streaming無効化時のクリーンアップ
148
+ // Cleanup when streaming is disabled
89
149
  if (this.realtimeStreamSubscriber) {
90
150
  this.streamPublisher.unsubscribe(this.realtimeStreamSubscriber.id);
91
151
  this.realtimeStreamSubscriber.destroy();
@@ -97,12 +157,12 @@ export class ProcessManager {
97
157
  }
98
158
  }
99
159
  }
100
- // Issue #13: RealtimeStreamSubscriber への参照を取得(新しいMCPツール用)
160
+ // Issue #13: Get RealtimeStreamSubscriber reference (for new MCP tools)
101
161
  getRealtimeStreamSubscriber() {
102
162
  return this.realtimeStreamSubscriber;
103
163
  }
104
164
  /**
105
- * Issue #13: output_idから実行IDを取得
165
+ * Issue #13: Get execution ID from output_id
106
166
  */
107
167
  findExecutionIdByOutputId(outputId) {
108
168
  return this.fileManager?.getExecutionIdByOutputId(outputId);
@@ -111,41 +171,70 @@ export class ProcessManager {
111
171
  await ensureDirectory(this.outputDir);
112
172
  }
113
173
  async executeCommand(options) {
114
- // 同時実行数のチェック
115
- const runningProcesses = Array.from(this.executions.values()).filter((exec) => exec.status === 'running').length;
174
+ let effectiveOptions = { ...options };
175
+ // For adaptive mode, when all slots are occupied, wait for a free slot
176
+ // using the same foreground wait budget requested by the caller.
177
+ const runningProcesses = this.getRunningExecutionCount();
116
178
  if (runningProcesses >= this.maxConcurrentProcesses) {
117
- throw new ResourceLimitError('concurrent processes', this.maxConcurrentProcesses);
179
+ const queueWaitBudgetSeconds = effectiveOptions.executionMode === 'adaptive'
180
+ ? (effectiveOptions.foregroundTimeoutSeconds ?? 10)
181
+ : 0;
182
+ const queueWaitBudgetMs = Math.max(0, Math.floor(queueWaitBudgetSeconds * 1000));
183
+ const waitStartedAt = Date.now();
184
+ const acquired = await this.waitForExecutionSlot(queueWaitBudgetMs);
185
+ const waitedMs = Date.now() - waitStartedAt;
186
+ if (!acquired) {
187
+ const currentRunning = this.getRunningExecutionCount();
188
+ const stopCandidates = this.buildConcurrencyStopCandidates();
189
+ throw new ResourceLimitError('concurrent processes', this.maxConcurrentProcesses, undefined, {
190
+ code: 'CONCURRENCY_LIMIT_EXCEEDED',
191
+ reason: `Concurrent execution limit reached (${currentRunning}/${this.maxConcurrentProcesses}) and no slot became available within ${Math.floor(waitedMs / 1000)}s queue wait budget.`,
192
+ running_count: currentRunning,
193
+ limit: this.maxConcurrentProcesses,
194
+ queue_wait_budget_seconds: queueWaitBudgetSeconds,
195
+ waited_seconds: Math.floor(waitedMs / 1000),
196
+ stop_candidates: stopCandidates,
197
+ next_steps: [
198
+ 'Call process_list with status_filter="running" to inspect active executions.',
199
+ 'Stop one or more long-running commands via process_kill, then retry shell_execute.',
200
+ ],
201
+ });
202
+ }
203
+ if (effectiveOptions.executionMode === 'adaptive') {
204
+ const originalForegroundWaitSeconds = effectiveOptions.foregroundTimeoutSeconds ?? 10;
205
+ const remainingForegroundWaitSeconds = Math.max(1, Math.floor((originalForegroundWaitSeconds * 1000 - waitedMs) / 1000));
206
+ effectiveOptions = {
207
+ ...effectiveOptions,
208
+ foregroundTimeoutSeconds: remainingForegroundWaitSeconds,
209
+ };
210
+ }
118
211
  }
119
- // 入力データの準備 - input_output_idが指定された場合の処理
120
- let resolvedInputData = options.inputData;
212
+ // Prepare input data when input_output_id is specified
213
+ let resolvedInputData = effectiveOptions.inputData;
121
214
  let inputStream = undefined;
122
- if (options.inputOutputId) {
215
+ if (effectiveOptions.inputOutputId) {
123
216
  if (!this.fileManager) {
124
217
  throw new ExecutionError('FileManager is not available for input_output_id processing', {
125
- inputOutputId: options.inputOutputId,
218
+ inputOutputId: effectiveOptions.inputOutputId,
126
219
  });
127
220
  }
128
- // output_idから実行IDを特定
129
- const sourceExecutionId = this.findExecutionIdByOutputId(options.inputOutputId);
221
+ const sourceExecutionId = this.findExecutionIdByOutputId(effectiveOptions.inputOutputId);
130
222
  if (sourceExecutionId && this.realtimeStreamSubscriber) {
131
- // 実行中プロセスの場合: StreamingPipelineReaderを使用
132
223
  const streamState = this.realtimeStreamSubscriber.getStreamState(sourceExecutionId);
133
224
  if (streamState && streamState.isActive) {
134
225
  console.error(`ProcessManager: Using streaming pipeline for active process ${sourceExecutionId}`);
135
- inputStream = new StreamingPipelineReader(this.fileManager, this.realtimeStreamSubscriber, options.inputOutputId, sourceExecutionId);
226
+ inputStream = new StreamingPipelineReader(this.fileManager, this.realtimeStreamSubscriber, effectiveOptions.inputOutputId, sourceExecutionId);
136
227
  }
137
228
  }
138
- // 実行中プロセスでない場合、または失敗した場合: 従来のファイル読み取り
139
229
  if (!inputStream) {
140
230
  try {
141
- console.error(`ProcessManager: Using traditional file read for ${options.inputOutputId}`);
142
- const result = await this.fileManager.readFile(options.inputOutputId, 0, 100 * 1024 * 1024, // 100MB まで読み取り
143
- 'utf-8');
231
+ console.error(`ProcessManager: Using traditional file read for ${effectiveOptions.inputOutputId}`);
232
+ const result = await this.fileManager.readFile(effectiveOptions.inputOutputId, 0, 100 * 1024 * 1024, 'utf-8');
144
233
  resolvedInputData = result.content;
145
234
  }
146
235
  catch (error) {
147
- throw new ExecutionError(`Failed to read input from output_id: ${options.inputOutputId}`, {
148
- inputOutputId: options.inputOutputId,
236
+ throw new ExecutionError(`Failed to read input from output_id: ${effectiveOptions.inputOutputId}`, {
237
+ inputOutputId: effectiveOptions.inputOutputId,
149
238
  originalError: String(error),
150
239
  });
151
240
  }
@@ -153,11 +242,10 @@ export class ProcessManager {
153
242
  }
154
243
  const executionId = generateId();
155
244
  const startTime = getCurrentTimestamp();
156
- // 実行情報の初期化
157
- const resolvedWorkingDirectory = this.resolveWorkingDirectory(options.workingDirectory);
245
+ const resolvedWorkingDirectory = this.resolveWorkingDirectory(effectiveOptions.workingDirectory);
158
246
  const executionInfo = {
159
247
  execution_id: executionId,
160
- command: options.command,
248
+ command: effectiveOptions.command,
161
249
  status: 'running',
162
250
  working_directory: resolvedWorkingDirectory,
163
251
  default_working_directory: this.defaultWorkingDirectory,
@@ -165,30 +253,27 @@ export class ProcessManager {
165
253
  created_at: startTime,
166
254
  started_at: startTime,
167
255
  };
168
- if (options.environmentVariables) {
169
- executionInfo.environment_variables = options.environmentVariables;
256
+ if (effectiveOptions.environmentVariables) {
257
+ executionInfo.environment_variables = effectiveOptions.environmentVariables;
170
258
  }
171
259
  this.executions.set(executionId, executionInfo);
172
- // 新規ターミナル作成オプションがある場合
173
- if (options.createTerminal && this.terminalManager) {
260
+ if (effectiveOptions.createTerminal && this.terminalManager) {
174
261
  try {
175
262
  const terminalOptions = {
176
263
  sessionName: `exec-${executionId}`,
177
- shellType: options.terminalShell || 'bash',
178
- dimensions: options.terminalDimensions || { width: 80, height: 24 },
264
+ shellType: effectiveOptions.terminalShell || 'bash',
265
+ dimensions: effectiveOptions.terminalDimensions || { width: 80, height: 24 },
179
266
  autoSaveHistory: true,
180
267
  };
181
- if (options.workingDirectory) {
182
- terminalOptions.workingDirectory = options.workingDirectory;
268
+ if (effectiveOptions.workingDirectory) {
269
+ terminalOptions.workingDirectory = effectiveOptions.workingDirectory;
183
270
  }
184
- if (options.environmentVariables) {
185
- terminalOptions.environmentVariables = options.environmentVariables;
271
+ if (effectiveOptions.environmentVariables) {
272
+ terminalOptions.environmentVariables = effectiveOptions.environmentVariables;
186
273
  }
187
274
  const terminalInfo = await this.terminalManager.createTerminal(terminalOptions);
188
275
  executionInfo.terminal_id = terminalInfo.terminal_id;
189
- // ターミナルにコマンドを送信
190
- this.terminalManager.sendInput(terminalInfo.terminal_id, options.command, true);
191
- // 実行情報を更新
276
+ this.terminalManager.sendInput(terminalInfo.terminal_id, effectiveOptions.command, true);
192
277
  executionInfo.status = 'completed';
193
278
  executionInfo.completed_at = getCurrentTimestamp();
194
279
  this.executions.set(executionId, executionInfo);
@@ -204,17 +289,15 @@ export class ProcessManager {
204
289
  }
205
290
  }
206
291
  try {
207
- // 実行オプションを準備
208
- const { inputOutputId: _inputOutputId, ...baseOptions } = options;
292
+ const { inputOutputId: _inputOutputId, ...baseOptions } = effectiveOptions;
209
293
  const updatedOptions = {
210
294
  ...baseOptions,
211
295
  ...(resolvedInputData !== undefined && { inputData: resolvedInputData }),
212
296
  };
213
- // StreamingPipelineReaderがある場合は特別処理
214
297
  if (inputStream) {
215
298
  return await this.executeCommandWithInputStream(executionId, updatedOptions, inputStream);
216
299
  }
217
- switch (options.executionMode) {
300
+ switch (effectiveOptions.executionMode) {
218
301
  case 'foreground':
219
302
  return await this.executeForegroundCommand(executionId, updatedOptions);
220
303
  case 'adaptive':
@@ -224,11 +307,10 @@ export class ProcessManager {
224
307
  case 'detached':
225
308
  return await this.executeDetachedCommand(executionId, updatedOptions);
226
309
  default:
227
- throw new ExecutionError('Unsupported execution mode', { mode: options.executionMode });
310
+ throw new ExecutionError('Unsupported execution mode', { mode: effectiveOptions.executionMode });
228
311
  }
229
312
  }
230
313
  catch (error) {
231
- // エラー時の実行情報更新
232
314
  const updatedInfo = this.executions.get(executionId);
233
315
  if (updatedInfo) {
234
316
  updatedInfo.status = 'failed';
@@ -239,7 +321,7 @@ export class ProcessManager {
239
321
  }
240
322
  }
241
323
  /**
242
- * Issue #13: StreamingPipelineReaderを使用したコマンド実行
324
+ * Issue #13: Execute command using StreamingPipelineReader
243
325
  */
244
326
  async executeCommandWithInputStream(executionId, options, inputStream) {
245
327
  console.error(`ProcessManager: Executing command with input stream for ${executionId}`);
@@ -248,15 +330,15 @@ export class ProcessManager {
248
330
  let stdout = '';
249
331
  let stderr = '';
250
332
  let outputTruncated = false;
251
- // 環境変数の準備
333
+ // Prepare environment variables
252
334
  const env = getSafeEnvironment(process.env, options.environmentVariables);
253
- // プロセスの起動
335
+ // Start process
254
336
  const child = spawn('sh', ['-c', options.command], {
255
337
  cwd: this.resolveWorkingDirectory(options.workingDirectory),
256
338
  env,
257
339
  stdio: ['pipe', 'pipe', 'pipe'],
258
340
  });
259
- // StreamingPipelineReaderSTDINに接続
341
+ // Connect StreamingPipelineReader to STDIN
260
342
  if (child.stdin) {
261
343
  inputStream.pipe(child.stdin);
262
344
  }
@@ -264,11 +346,11 @@ export class ProcessManager {
264
346
  console.error(`StreamingPipelineReader error for ${executionId}: ${error.message}`);
265
347
  child.kill('SIGTERM');
266
348
  });
267
- // StreamPublisher通知
349
+ // Notify StreamPublisher
268
350
  if (this.streamPublisher) {
269
351
  this.streamPublisher.notifyProcessStart(executionId, options.command);
270
352
  }
271
- // STDOUT処理
353
+ // Handle STDOUT
272
354
  if (child.stdout) {
273
355
  child.stdout.on('data', (data) => {
274
356
  const chunk = data.toString();
@@ -278,13 +360,13 @@ export class ProcessManager {
278
360
  else {
279
361
  outputTruncated = true;
280
362
  }
281
- // StreamPublisher通知
363
+ // Notify StreamPublisher
282
364
  if (this.streamPublisher) {
283
365
  this.streamPublisher.notifyOutputData(executionId, chunk, false);
284
366
  }
285
367
  });
286
368
  }
287
- // STDERR処理
369
+ // Handle STDERR
288
370
  if (options.captureStderr && child.stderr) {
289
371
  child.stderr.on('data', (data) => {
290
372
  const chunk = data.toString();
@@ -294,29 +376,29 @@ export class ProcessManager {
294
376
  else {
295
377
  outputTruncated = true;
296
378
  }
297
- // StreamPublisher通知
379
+ // Notify StreamPublisher
298
380
  if (this.streamPublisher) {
299
381
  this.streamPublisher.notifyOutputData(executionId, chunk, true);
300
382
  }
301
383
  });
302
384
  }
303
- // プロセス終了処理
385
+ // Handle process completion
304
386
  child.on('close', async (code) => {
305
387
  const executionInfo = this.executions.get(executionId);
306
388
  if (!executionInfo) {
307
389
  reject(new ExecutionError('Execution info not found', { executionId }));
308
390
  return;
309
391
  }
310
- // 実行時間の計算
392
+ // Calculate execution time
311
393
  const executionTime = Date.now() - startTime;
312
- // 実行情報の更新
394
+ // Update execution info
313
395
  executionInfo.status = code === 0 ? 'completed' : 'failed';
314
396
  executionInfo.completed_at = getCurrentTimestamp();
315
397
  if (code !== null) {
316
398
  executionInfo.exit_code = code;
317
399
  }
318
400
  executionInfo.execution_time_ms = executionTime;
319
- // 出力の保存
401
+ // Save output
320
402
  if (this.fileManager) {
321
403
  try {
322
404
  const combinedOutput = stdout + (options.captureStderr ? stderr : '');
@@ -331,7 +413,7 @@ export class ProcessManager {
331
413
  }
332
414
  }
333
415
  this.executions.set(executionId, executionInfo);
334
- // StreamPublisher通知
416
+ // Notify StreamPublisher
335
417
  if (this.streamPublisher) {
336
418
  this.streamPublisher.notifyProcessEnd(executionId, code);
337
419
  }
@@ -340,24 +422,28 @@ export class ProcessManager {
340
422
  });
341
423
  child.on('error', (error) => {
342
424
  console.error(`Process error for ${executionId}: ${error.message}`);
343
- // StreamPublisher通知
425
+ // Notify StreamPublisher
344
426
  if (this.streamPublisher) {
345
427
  this.streamPublisher.notifyError(executionId, error);
346
428
  }
347
429
  reject(new ExecutionError(`Process error: ${error.message}`, { originalError: String(error) }));
348
430
  });
349
- // タイムアウト処理
350
- const timeout = setTimeout(() => {
351
- console.error(`Process timeout for ${executionId}`);
352
- child.kill('SIGTERM');
353
- setTimeout(() => {
354
- if (!child.killed) {
355
- child.kill('SIGKILL');
356
- }
357
- }, 5000);
358
- }, options.timeoutSeconds * 1000);
431
+ // Timeout handling
432
+ const timeout = options.timeoutSeconds !== undefined
433
+ ? setTimeout(() => {
434
+ console.error(`Process timeout for ${executionId}`);
435
+ child.kill('SIGTERM');
436
+ setTimeout(() => {
437
+ if (!child.killed) {
438
+ child.kill('SIGKILL');
439
+ }
440
+ }, 5000);
441
+ }, options.timeoutSeconds * 1000)
442
+ : undefined;
359
443
  child.on('close', () => {
360
- clearTimeout(timeout);
444
+ if (timeout) {
445
+ clearTimeout(timeout);
446
+ }
361
447
  });
362
448
  });
363
449
  }
@@ -367,9 +453,9 @@ export class ProcessManager {
367
453
  let stdout = '';
368
454
  let stderr = '';
369
455
  let outputTruncated = false;
370
- // 環境変数の準備
456
+ // Prepare environment variables
371
457
  const env = getSafeEnvironment(process.env, options.environmentVariables);
372
- // プロセスの起動
458
+ // Start process
373
459
  const childProcess = spawn('/bin/bash', ['-c', options.command], {
374
460
  cwd: this.resolveWorkingDirectory(options.workingDirectory),
375
461
  env,
@@ -378,48 +464,50 @@ export class ProcessManager {
378
464
  if (childProcess.pid) {
379
465
  this.processes.set(childProcess.pid, childProcess);
380
466
  }
381
- // タイムアウトの設定
382
- const timeout = setTimeout(async () => {
383
- childProcess.kill('SIGTERM');
384
- setTimeout(() => {
385
- if (!childProcess.killed) {
386
- childProcess.kill('SIGKILL');
387
- }
388
- }, 5000);
389
- const executionTime = Date.now() - startTime;
390
- const executionInfo = this.executions.get(executionId);
391
- if (executionInfo) {
392
- executionInfo.status = 'timeout';
393
- executionInfo.stdout = sanitizeString(stdout);
394
- executionInfo.stderr = sanitizeString(stderr);
395
- executionInfo.completed_at = getCurrentTimestamp();
396
- executionInfo.execution_time_ms = executionTime;
397
- if (childProcess.pid !== undefined) {
398
- executionInfo.process_id = childProcess.pid;
399
- }
400
- // 出力をFileManagerに保存(サイズに関係なく)
401
- let outputFileId;
402
- try {
403
- outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
404
- executionInfo.output_id = outputFileId;
405
- }
406
- catch (error) {
407
- // ファイル保存失敗は重要なエラーとしてログに記録し、実行情報に含める
408
- console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
409
- executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
410
- }
411
- // 出力状態の詳細情報を設定
412
- this.setOutputStatus(executionInfo, outputTruncated, 'timeout', outputFileId);
413
- this.executions.set(executionId, executionInfo);
414
- // return_partial_on_timeout が true の場合は部分結果を返す
415
- if (options.returnPartialOnTimeout) {
416
- resolve(executionInfo);
417
- return;
467
+ // Set timeout
468
+ const timeout = options.timeoutSeconds !== undefined
469
+ ? setTimeout(async () => {
470
+ childProcess.kill('SIGTERM');
471
+ setTimeout(() => {
472
+ if (!childProcess.killed) {
473
+ childProcess.kill('SIGKILL');
474
+ }
475
+ }, 5000);
476
+ const executionTime = Date.now() - startTime;
477
+ const executionInfo = this.executions.get(executionId);
478
+ if (executionInfo) {
479
+ executionInfo.status = 'timeout';
480
+ executionInfo.stdout = sanitizeString(stdout);
481
+ executionInfo.stderr = sanitizeString(stderr);
482
+ executionInfo.completed_at = getCurrentTimestamp();
483
+ executionInfo.execution_time_ms = executionTime;
484
+ if (childProcess.pid !== undefined) {
485
+ executionInfo.process_id = childProcess.pid;
486
+ }
487
+ // Save output to FileManager (regardless of size)
488
+ let outputFileId;
489
+ try {
490
+ outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
491
+ executionInfo.output_id = outputFileId;
492
+ }
493
+ catch (error) {
494
+ // Record file-save failures as critical errors and include them in execution info
495
+ console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
496
+ executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
497
+ }
498
+ // Set detailed output status
499
+ this.setOutputStatus(executionInfo, outputTruncated, 'timeout', outputFileId);
500
+ this.executions.set(executionId, executionInfo);
501
+ // Return partial result when return_partial_on_timeout is true
502
+ if (options.returnPartialOnTimeout) {
503
+ resolve(executionInfo);
504
+ return;
505
+ }
418
506
  }
419
- }
420
- reject(new TimeoutError(options.timeoutSeconds));
421
- }, options.timeoutSeconds * 1000);
422
- // 標準入力の送信
507
+ reject(new TimeoutError(options.timeoutSeconds ?? 0));
508
+ }, options.timeoutSeconds * 1000)
509
+ : undefined;
510
+ // Send stdin
423
511
  if (options.inputData) {
424
512
  childProcess.stdin?.write(options.inputData);
425
513
  childProcess.stdin?.end();
@@ -427,7 +515,7 @@ export class ProcessManager {
427
515
  else {
428
516
  childProcess.stdin?.end();
429
517
  }
430
- // 標準出力の処理
518
+ // Handle stdout
431
519
  childProcess.stdout?.on('data', (data) => {
432
520
  const output = data.toString();
433
521
  if (stdout.length + output.length <= options.maxOutputSize) {
@@ -438,7 +526,7 @@ export class ProcessManager {
438
526
  outputTruncated = true;
439
527
  }
440
528
  });
441
- // 標準エラー出力の処理
529
+ // Handle stderr
442
530
  if (options.captureStderr) {
443
531
  childProcess.stderr?.on('data', (data) => {
444
532
  const output = data.toString();
@@ -451,9 +539,11 @@ export class ProcessManager {
451
539
  }
452
540
  });
453
541
  }
454
- // プロセス終了時の処理
542
+ // Handle process close
455
543
  childProcess.on('close', async (code) => {
456
- clearTimeout(timeout);
544
+ if (timeout) {
545
+ clearTimeout(timeout);
546
+ }
457
547
  if (childProcess.pid) {
458
548
  this.processes.delete(childProcess.pid);
459
549
  }
@@ -469,30 +559,30 @@ export class ProcessManager {
469
559
  executionInfo.process_id = childProcess.pid;
470
560
  }
471
561
  executionInfo.completed_at = getCurrentTimestamp();
472
- // 出力をFileManagerに保存(サイズに関係なく)
562
+ // Save output to FileManager (regardless of size)
473
563
  let outputFileId;
474
564
  try {
475
565
  outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
476
566
  executionInfo.output_id = outputFileId;
477
567
  }
478
568
  catch (error) {
479
- // ファイル保存失敗は重要なエラーとしてログに記録し、実行情報に含める
569
+ // Record file-save failures as critical errors and include them in execution info
480
570
  console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
481
571
  executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
482
572
  }
483
- // 出力状態の詳細情報を設定
573
+ // Set detailed output status
484
574
  if (outputTruncated) {
485
575
  this.setOutputStatus(executionInfo, true, 'size_limit', outputFileId);
486
576
  }
487
577
  else {
488
- // 通常完了時 - actuallyTruncated=false, 適当なreasonで完了時ガイダンスを表示
578
+ // Normal completion: actuallyTruncated=false, use a valid reason to show completion guidance
489
579
  this.setOutputStatus(executionInfo, false, 'size_limit', outputFileId);
490
580
  }
491
581
  this.executions.set(executionId, executionInfo);
492
582
  resolve(executionInfo);
493
583
  }
494
584
  });
495
- // エラー処理
585
+ // Error handling
496
586
  childProcess.on('error', (error) => {
497
587
  clearTimeout(timeout);
498
588
  if (childProcess.pid) {
@@ -512,9 +602,9 @@ export class ProcessManager {
512
602
  });
513
603
  }
514
604
  async executeAdaptiveCommand(executionId, options) {
515
- // adaptiveモード: 1つのプロセスを起動し、以下の条件でバックグラウンドに移行
516
- // 1. フォアグラウンドタイムアウトに達した場合
517
- // 2. 出力サイズ制限に達した場合
605
+ // Adaptive mode: start one process and transition to background when:
606
+ // 1. Foreground timeout is reached
607
+ // 2. Output size limit is reached
518
608
  const returnPartialOnTimeout = options.returnPartialOnTimeout ?? true;
519
609
  const foregroundTimeout = options.foregroundTimeoutSeconds ?? 10;
520
610
  return new Promise((resolve, reject) => {
@@ -523,9 +613,9 @@ export class ProcessManager {
523
613
  let stderr = '';
524
614
  let outputTruncated = false;
525
615
  let backgroundTransitionReason = null;
526
- // 環境変数の準備
616
+ // Prepare environment variables
527
617
  const env = getSafeEnvironment(process.env, options.environmentVariables);
528
- // プロセスの起動(バックグラウンド対応)
618
+ // Start process (supports background transition)
529
619
  const childProcess = spawn('/bin/bash', ['-c', options.command], {
530
620
  cwd: this.resolveWorkingDirectory(options.workingDirectory),
531
621
  env,
@@ -534,48 +624,50 @@ export class ProcessManager {
534
624
  if (childProcess.pid) {
535
625
  this.processes.set(childProcess.pid, childProcess);
536
626
  }
537
- // フォアグラウンドタイムアウトの設定
627
+ // Set foreground timeout
538
628
  const foregroundTimeoutHandle = setTimeout(() => {
539
629
  if (!backgroundTransitionReason) {
540
630
  backgroundTransitionReason = 'timeout';
541
631
  transitionToBackground();
542
632
  }
543
633
  }, foregroundTimeout * 1000);
544
- // 最終タイムアウトの設定
545
- const finalTimeoutHandle = setTimeout(async () => {
546
- childProcess.kill('SIGTERM');
547
- setTimeout(() => {
548
- if (!childProcess.killed) {
549
- childProcess.kill('SIGKILL');
550
- }
551
- }, 5000);
552
- const executionInfo = this.executions.get(executionId);
553
- if (executionInfo) {
554
- executionInfo.status = 'timeout';
555
- executionInfo.stdout = sanitizeString(stdout);
556
- executionInfo.stderr = sanitizeString(stderr);
557
- executionInfo.output_truncated = outputTruncated;
558
- executionInfo.completed_at = getCurrentTimestamp();
559
- executionInfo.execution_time_ms = Date.now() - startTime;
560
- // 出力をFileManagerに保存
561
- try {
562
- const outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
563
- executionInfo.output_id = outputFileId;
564
- }
565
- catch (error) {
566
- // ファイル保存失敗は重要なエラーとしてログに記録し、実行情報に含める
567
- console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
568
- executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
569
- }
570
- this.executions.set(executionId, executionInfo);
571
- if (returnPartialOnTimeout) {
572
- resolve(executionInfo);
573
- return;
634
+ // Set final timeout
635
+ const finalTimeoutHandle = options.timeoutSeconds !== undefined
636
+ ? setTimeout(async () => {
637
+ childProcess.kill('SIGTERM');
638
+ setTimeout(() => {
639
+ if (!childProcess.killed) {
640
+ childProcess.kill('SIGKILL');
641
+ }
642
+ }, 5000);
643
+ const executionInfo = this.executions.get(executionId);
644
+ if (executionInfo) {
645
+ executionInfo.status = 'timeout';
646
+ executionInfo.stdout = sanitizeString(stdout);
647
+ executionInfo.stderr = sanitizeString(stderr);
648
+ executionInfo.output_truncated = outputTruncated;
649
+ executionInfo.completed_at = getCurrentTimestamp();
650
+ executionInfo.execution_time_ms = Date.now() - startTime;
651
+ // Save output to FileManager
652
+ try {
653
+ const outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
654
+ executionInfo.output_id = outputFileId;
655
+ }
656
+ catch (error) {
657
+ // Record file-save failures as critical errors and include them in execution info
658
+ console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
659
+ executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
660
+ }
661
+ this.executions.set(executionId, executionInfo);
662
+ if (returnPartialOnTimeout) {
663
+ resolve(executionInfo);
664
+ return;
665
+ }
574
666
  }
575
- }
576
- reject(new TimeoutError(options.timeoutSeconds));
577
- }, options.timeoutSeconds * 1000);
578
- // バックグラウンドに移行する関数
667
+ reject(new TimeoutError(options.timeoutSeconds ?? 0));
668
+ }, options.timeoutSeconds * 1000)
669
+ : undefined;
670
+ // Function to transition to background mode
579
671
  const transitionToBackground = async () => {
580
672
  clearTimeout(foregroundTimeoutHandle);
581
673
  const executionInfo = this.executions.get(executionId);
@@ -583,7 +675,7 @@ export class ProcessManager {
583
675
  executionInfo.status = 'running';
584
676
  executionInfo.stdout = sanitizeString(stdout);
585
677
  executionInfo.stderr = sanitizeString(stderr);
586
- // 移行理由を記録
678
+ // Record transition reason
587
679
  if (backgroundTransitionReason === 'timeout') {
588
680
  executionInfo.transition_reason = 'foreground_timeout';
589
681
  }
@@ -593,29 +685,34 @@ export class ProcessManager {
593
685
  if (childProcess.pid !== undefined) {
594
686
  executionInfo.process_id = childProcess.pid;
595
687
  }
596
- // 出力をFileManagerに保存
688
+ // Save output to FileManager
597
689
  let outputFileId;
598
690
  try {
599
691
  outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
600
692
  executionInfo.output_id = outputFileId;
601
693
  }
602
694
  catch (error) {
603
- // ファイル保存失敗は重要なエラーとしてログに記録し、実行情報に含める
695
+ // Record file-save failures as critical errors and include them in execution info
604
696
  console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
605
697
  executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
606
698
  }
607
- // 出力状態の詳細情報を設定(バックグラウンド移行)
699
+ // Set detailed output status (background transition)
608
700
  this.setOutputStatus(executionInfo, outputTruncated, 'background_transition', outputFileId);
609
701
  this.executions.set(executionId, executionInfo);
610
- // バックグラウンド処理の継続設定(adaptive mode専用)
702
+ // Configure continued background handling (adaptive mode only)
703
+ const remainingTimeoutSeconds = options.timeoutSeconds !== undefined
704
+ ? Math.max(1, options.timeoutSeconds - Math.floor((Date.now() - startTime) / 1000))
705
+ : undefined;
611
706
  this.handleAdaptiveBackgroundTransition(executionId, childProcess, {
612
707
  ...options,
613
- timeoutSeconds: Math.max(1, options.timeoutSeconds - Math.floor((Date.now() - startTime) / 1000)),
708
+ ...(remainingTimeoutSeconds !== undefined
709
+ ? { timeoutSeconds: remainingTimeoutSeconds }
710
+ : {}),
614
711
  });
615
712
  resolve(executionInfo);
616
713
  }
617
714
  };
618
- // 標準入力の送信
715
+ // Send stdin
619
716
  if (options.inputData) {
620
717
  childProcess.stdin?.write(options.inputData);
621
718
  childProcess.stdin?.end();
@@ -623,7 +720,7 @@ export class ProcessManager {
623
720
  else {
624
721
  childProcess.stdin?.end();
625
722
  }
626
- // 標準出力の処理
723
+ // Handle stdout
627
724
  childProcess.stdout?.on('data', (data) => {
628
725
  const output = data.toString();
629
726
  if (stdout.length + output.length <= options.maxOutputSize) {
@@ -632,14 +729,14 @@ export class ProcessManager {
632
729
  else {
633
730
  stdout += output.substring(0, options.maxOutputSize - stdout.length);
634
731
  outputTruncated = true;
635
- // 出力サイズ制限に達した場合、バックグラウンドに移行
732
+ // Transition to background when output size limit is reached
636
733
  if (!backgroundTransitionReason) {
637
734
  backgroundTransitionReason = 'output_size_limit';
638
735
  transitionToBackground();
639
736
  }
640
737
  }
641
738
  });
642
- // 標準エラー出力の処理
739
+ // Handle stderr
643
740
  if (options.captureStderr) {
644
741
  childProcess.stderr?.on('data', (data) => {
645
742
  const output = data.toString();
@@ -649,7 +746,7 @@ export class ProcessManager {
649
746
  else {
650
747
  stderr += output.substring(0, options.maxOutputSize - stderr.length);
651
748
  outputTruncated = true;
652
- // 出力サイズ制限に達した場合、バックグラウンドに移行
749
+ // Transition to background when output size limit is reached
653
750
  if (!backgroundTransitionReason) {
654
751
  backgroundTransitionReason = 'output_size_limit';
655
752
  transitionToBackground();
@@ -657,14 +754,16 @@ export class ProcessManager {
657
754
  }
658
755
  });
659
756
  }
660
- // プロセス終了時の処理
757
+ // Handle process close
661
758
  childProcess.on('close', async (code) => {
662
759
  clearTimeout(foregroundTimeoutHandle);
663
- clearTimeout(finalTimeoutHandle);
760
+ if (finalTimeoutHandle) {
761
+ clearTimeout(finalTimeoutHandle);
762
+ }
664
763
  if (childProcess.pid) {
665
764
  this.processes.delete(childProcess.pid);
666
765
  }
667
- // バックグラウンドに移行していない場合のみ処理
766
+ // Handle only when no background transition occurred
668
767
  if (!backgroundTransitionReason) {
669
768
  const executionTime = Date.now() - startTime;
670
769
  const executionInfo = this.executions.get(executionId);
@@ -679,13 +778,13 @@ export class ProcessManager {
679
778
  executionInfo.process_id = childProcess.pid;
680
779
  }
681
780
  executionInfo.completed_at = getCurrentTimestamp();
682
- // 出力をFileManagerに保存
781
+ // Save output to FileManager
683
782
  try {
684
783
  const outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
685
784
  executionInfo.output_id = outputFileId;
686
785
  }
687
786
  catch (error) {
688
- // ファイル保存失敗は重要なエラーとしてログに記録し、実行情報に含める
787
+ // Record file-save failures as critical errors and include them in execution info
689
788
  console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
690
789
  executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
691
790
  }
@@ -694,10 +793,12 @@ export class ProcessManager {
694
793
  }
695
794
  }
696
795
  });
697
- // エラー処理
796
+ // Error handling
698
797
  childProcess.on('error', (error) => {
699
798
  clearTimeout(foregroundTimeoutHandle);
700
- clearTimeout(finalTimeoutHandle);
799
+ if (finalTimeoutHandle) {
800
+ clearTimeout(finalTimeoutHandle);
801
+ }
701
802
  if (childProcess.pid) {
702
803
  this.processes.delete(childProcess.pid);
703
804
  }
@@ -732,7 +833,7 @@ export class ProcessManager {
732
833
  executionInfo.process_id = childProcess.pid;
733
834
  this.executions.set(executionId, executionInfo);
734
835
  }
735
- // バックグラウンドプロセスの場合、出力を非同期で処理
836
+ // For background processes, handle output asynchronously
736
837
  if (options.executionMode === 'background') {
737
838
  this.handleBackgroundProcess(executionId, childProcess, options);
738
839
  }
@@ -746,54 +847,56 @@ export class ProcessManager {
746
847
  const startTime = Date.now();
747
848
  let stdout = '';
748
849
  let stderr = '';
749
- // タイムアウトの設定(backgroundプロセス用)
750
- const timeout = setTimeout(async () => {
751
- childProcess.kill('SIGTERM');
752
- setTimeout(() => {
753
- if (!childProcess.killed) {
754
- childProcess.kill('SIGKILL');
755
- }
756
- }, 5000);
757
- const executionInfo = this.executions.get(executionId);
758
- if (executionInfo) {
759
- executionInfo.status = 'timeout';
760
- executionInfo.stdout = stdout;
761
- executionInfo.stderr = stderr;
762
- executionInfo.output_truncated = true;
763
- executionInfo.completed_at = getCurrentTimestamp();
764
- executionInfo.execution_time_ms = Date.now() - startTime;
765
- // 出力をFileManagerに保存
766
- try {
767
- const outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
768
- executionInfo.output_id = outputFileId;
769
- }
770
- catch (error) {
771
- // ファイル保存失敗は重要なエラーとしてログに記録し、実行情報に含める
772
- console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
773
- executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
774
- }
775
- this.executions.set(executionId, executionInfo);
776
- // バックグラウンドプロセスタイムアウトのコールバック呼び出し
777
- if (this.backgroundProcessCallbacks.onTimeout) {
778
- setImmediate(async () => {
779
- try {
780
- const callback = this.backgroundProcessCallbacks.onTimeout;
781
- if (callback) {
782
- const result = callback(executionId, executionInfo);
783
- if (result instanceof Promise) {
784
- await result;
850
+ // Set timeout (for background processes)
851
+ const timeout = options.timeoutSeconds !== undefined
852
+ ? setTimeout(async () => {
853
+ childProcess.kill('SIGTERM');
854
+ setTimeout(() => {
855
+ if (!childProcess.killed) {
856
+ childProcess.kill('SIGKILL');
857
+ }
858
+ }, 5000);
859
+ const executionInfo = this.executions.get(executionId);
860
+ if (executionInfo) {
861
+ executionInfo.status = 'timeout';
862
+ executionInfo.stdout = stdout;
863
+ executionInfo.stderr = stderr;
864
+ executionInfo.output_truncated = true;
865
+ executionInfo.completed_at = getCurrentTimestamp();
866
+ executionInfo.execution_time_ms = Date.now() - startTime;
867
+ // Save output to FileManager
868
+ try {
869
+ const outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
870
+ executionInfo.output_id = outputFileId;
871
+ }
872
+ catch (error) {
873
+ // Record file-save failures as critical errors and include them in execution info
874
+ console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
875
+ executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
876
+ }
877
+ this.executions.set(executionId, executionInfo);
878
+ // Invoke timeout callback for background process
879
+ if (this.backgroundProcessCallbacks.onTimeout) {
880
+ setImmediate(async () => {
881
+ try {
882
+ const callback = this.backgroundProcessCallbacks.onTimeout;
883
+ if (callback) {
884
+ const result = callback(executionId, executionInfo);
885
+ if (result instanceof Promise) {
886
+ await result;
887
+ }
785
888
  }
786
889
  }
787
- }
788
- catch (callbackError) {
789
- // コールバックエラーは内部ログに記録のみ
790
- // console.error('Background process timeout callback error:', callbackError);
791
- }
792
- });
890
+ catch (callbackError) {
891
+ // Record callback errors in internal logs only
892
+ // console.error('Background process timeout callback error:', callbackError);
893
+ }
894
+ });
895
+ }
793
896
  }
794
- }
795
- }, options.timeoutSeconds * 1000);
796
- // 出力の収集
897
+ }, options.timeoutSeconds * 1000)
898
+ : undefined;
899
+ // Collect output
797
900
  childProcess.stdout?.on('data', (data) => {
798
901
  stdout += data.toString();
799
902
  });
@@ -802,9 +905,11 @@ export class ProcessManager {
802
905
  stderr += data.toString();
803
906
  });
804
907
  }
805
- // プロセス終了時の処理
908
+ // Handle process close
806
909
  childProcess.on('close', async (code) => {
807
- clearTimeout(timeout);
910
+ if (timeout) {
911
+ clearTimeout(timeout);
912
+ }
808
913
  if (childProcess.pid) {
809
914
  this.processes.delete(childProcess.pid);
810
915
  }
@@ -814,18 +919,18 @@ export class ProcessManager {
814
919
  executionInfo.exit_code = code || 0;
815
920
  executionInfo.execution_time_ms = Date.now() - startTime;
816
921
  executionInfo.completed_at = getCurrentTimestamp();
817
- // 出力をファイルに保存
922
+ // Save output to file
818
923
  try {
819
924
  const outputFileId = await this.saveOutputToFile(executionId, stdout, stderr);
820
925
  executionInfo.output_id = outputFileId;
821
926
  }
822
927
  catch (error) {
823
- // ファイル保存失敗は重要なエラーとしてログに記録し、実行情報に含める
928
+ // Record file-save failures as critical errors and include them in execution info
824
929
  console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
825
930
  executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
826
931
  }
827
932
  this.executions.set(executionId, executionInfo);
828
- // バックグラウンドプロセス正常終了のコールバック呼び出し
933
+ // Invoke completion callback for background process
829
934
  if (this.backgroundProcessCallbacks.onComplete) {
830
935
  setImmediate(async () => {
831
936
  try {
@@ -838,7 +943,7 @@ export class ProcessManager {
838
943
  }
839
944
  }
840
945
  catch (callbackError) {
841
- // コールバックエラーは内部ログに記録のみ
946
+ // Record callback errors in internal logs only
842
947
  // console.error('Background process completion callback error:', callbackError);
843
948
  }
844
949
  });
@@ -846,7 +951,9 @@ export class ProcessManager {
846
951
  }
847
952
  });
848
953
  childProcess.on('error', (error) => {
849
- clearTimeout(timeout);
954
+ if (timeout) {
955
+ clearTimeout(timeout);
956
+ }
850
957
  if (childProcess.pid) {
851
958
  this.processes.delete(childProcess.pid);
852
959
  }
@@ -856,7 +963,7 @@ export class ProcessManager {
856
963
  executionInfo.execution_time_ms = Date.now() - startTime;
857
964
  executionInfo.completed_at = getCurrentTimestamp();
858
965
  this.executions.set(executionId, executionInfo);
859
- // バックグラウンドプロセスエラーのコールバック呼び出し
966
+ // Invoke error callback for background process
860
967
  if (this.backgroundProcessCallbacks.onError) {
861
968
  setImmediate(async () => {
862
969
  try {
@@ -869,7 +976,7 @@ export class ProcessManager {
869
976
  }
870
977
  }
871
978
  catch (callbackError) {
872
- // コールバックエラーは内部ログに記録のみ
979
+ // Record callback errors in internal logs only
873
980
  // console.error('Background process error callback error:', callbackError);
874
981
  }
875
982
  });
@@ -877,27 +984,31 @@ export class ProcessManager {
877
984
  }
878
985
  });
879
986
  }
880
- // adaptive modeでバックグラウンドに移行したプロセスの処理
987
+ // Handle processes transitioned to background in adaptive mode
881
988
  handleAdaptiveBackgroundTransition(executionId, childProcess, options) {
882
- // タイムアウトの設定(最終タイムアウト)
883
- const timeout = setTimeout(async () => {
884
- childProcess.kill('SIGTERM');
885
- setTimeout(() => {
886
- if (!childProcess.killed) {
887
- childProcess.kill('SIGKILL');
989
+ // Set timeout (final timeout)
990
+ const timeout = options.timeoutSeconds !== undefined
991
+ ? setTimeout(async () => {
992
+ childProcess.kill('SIGTERM');
993
+ setTimeout(() => {
994
+ if (!childProcess.killed) {
995
+ childProcess.kill('SIGKILL');
996
+ }
997
+ }, 5000);
998
+ const executionInfo = this.executions.get(executionId);
999
+ if (executionInfo) {
1000
+ executionInfo.status = 'timeout';
1001
+ executionInfo.completed_at = getCurrentTimestamp();
1002
+ // Keep existing output (already captured in adaptive mode)
1003
+ this.executions.set(executionId, executionInfo);
888
1004
  }
889
- }, 5000);
890
- const executionInfo = this.executions.get(executionId);
891
- if (executionInfo) {
892
- executionInfo.status = 'timeout';
893
- executionInfo.completed_at = getCurrentTimestamp();
894
- // 既存の出力は保持(adaptive modeで既にキャプチャ済み)
895
- this.executions.set(executionId, executionInfo);
896
- }
897
- }, options.timeoutSeconds * 1000);
898
- // プロセス終了時の処理
1005
+ }, options.timeoutSeconds * 1000)
1006
+ : undefined;
1007
+ // Handle process close
899
1008
  childProcess.on('close', async (code) => {
900
- clearTimeout(timeout);
1009
+ if (timeout) {
1010
+ clearTimeout(timeout);
1011
+ }
901
1012
  if (childProcess.pid) {
902
1013
  this.processes.delete(childProcess.pid);
903
1014
  }
@@ -906,13 +1017,13 @@ export class ProcessManager {
906
1017
  executionInfo.status = 'completed';
907
1018
  executionInfo.exit_code = code || 0;
908
1019
  executionInfo.completed_at = getCurrentTimestamp();
909
- // 実行時間は全体(フォアグラウンド + バックグラウンド)で計算
1020
+ // Calculate total execution time (foreground + background)
910
1021
  if (executionInfo.started_at) {
911
1022
  const startTime = new Date(executionInfo.started_at).getTime();
912
1023
  executionInfo.execution_time_ms = Date.now() - startTime;
913
1024
  }
914
1025
  this.executions.set(executionId, executionInfo);
915
- // adaptive modeバックグラウンドプロセス正常終了のコールバック呼び出し
1026
+ // Invoke completion callback for adaptive-mode background process
916
1027
  if (this.backgroundProcessCallbacks.onComplete) {
917
1028
  setImmediate(async () => {
918
1029
  try {
@@ -925,7 +1036,7 @@ export class ProcessManager {
925
1036
  }
926
1037
  }
927
1038
  catch (callbackError) {
928
- // コールバックエラーは内部ログに記録のみ
1039
+ // Record callback errors in internal logs only
929
1040
  // console.error('Adaptive background process completion callback error:', callbackError);
930
1041
  }
931
1042
  });
@@ -933,7 +1044,9 @@ export class ProcessManager {
933
1044
  }
934
1045
  });
935
1046
  childProcess.on('error', (error) => {
936
- clearTimeout(timeout);
1047
+ if (timeout) {
1048
+ clearTimeout(timeout);
1049
+ }
937
1050
  if (childProcess.pid) {
938
1051
  this.processes.delete(childProcess.pid);
939
1052
  }
@@ -946,7 +1059,7 @@ export class ProcessManager {
946
1059
  executionInfo.execution_time_ms = Date.now() - startTime;
947
1060
  }
948
1061
  this.executions.set(executionId, executionInfo);
949
- // adaptive modeバックグラウンドプロセスエラーのコールバック呼び出し
1062
+ // Invoke error callback for adaptive-mode background process
950
1063
  if (this.backgroundProcessCallbacks.onError) {
951
1064
  setImmediate(async () => {
952
1065
  try {
@@ -959,7 +1072,7 @@ export class ProcessManager {
959
1072
  }
960
1073
  }
961
1074
  catch (callbackError) {
962
- // コールバックエラーは内部ログに記録のみ
1075
+ // Record callback errors in internal logs only
963
1076
  // console.error('Adaptive background process error callback error:', callbackError);
964
1077
  }
965
1078
  });
@@ -968,23 +1081,23 @@ export class ProcessManager {
968
1081
  });
969
1082
  }
970
1083
  async executeDetachedCommand(executionId, options) {
971
- // detachedモード: 完全にバックグラウンドで実行し、親プロセスとの接続を切断
1084
+ // Detached mode: run fully in background and detach from parent process
972
1085
  const env = getSafeEnvironment(process.env, options.environmentVariables);
973
1086
  const childProcess = spawn('/bin/bash', ['-c', options.command], {
974
1087
  cwd: this.resolveWorkingDirectory(options.workingDirectory),
975
1088
  env,
976
- stdio: ['ignore', 'pipe', 'pipe'], // stdin は無視
977
- detached: true, // 完全にデタッチ
1089
+ stdio: ['ignore', 'pipe', 'pipe'], // ignore stdin
1090
+ detached: true, // fully detached
978
1091
  });
979
- // デタッチされたプロセスのPIDは記録するが、プロセス管理からは除外
1092
+ // Record detached process PID but exclude it from process management
980
1093
  const executionInfo = this.executions.get(executionId);
981
1094
  if (executionInfo && childProcess.pid !== undefined) {
982
1095
  executionInfo.process_id = childProcess.pid;
983
1096
  executionInfo.status = 'running';
984
1097
  this.executions.set(executionId, executionInfo);
985
1098
  }
986
- // デタッチされたプロセスは親プロセスの終了後も継続実行されるため、
987
- // 出力の収集は限定的
1099
+ // Detached processes continue after parent exits,
1100
+ // so output collection is limited
988
1101
  const startTime = Date.now();
989
1102
  let stdout = '';
990
1103
  let stderr = '';
@@ -998,7 +1111,7 @@ export class ProcessManager {
998
1111
  stderr += data.toString();
999
1112
  });
1000
1113
  }
1001
- // プロセスの終了を監視(デタッチされているため必ずしも捕捉されない)
1114
+ // Monitor process exit (not always capturable when detached)
1002
1115
  childProcess.on('close', async (code) => {
1003
1116
  const executionInfo = this.executions.get(executionId);
1004
1117
  if (executionInfo) {
@@ -1011,12 +1124,12 @@ export class ProcessManager {
1011
1124
  executionInfo.output_id = outputFileId;
1012
1125
  }
1013
1126
  catch (error) {
1014
- // ファイル保存失敗は重要なエラーとしてログに記録し、実行情報に含める
1127
+ // Record file-save failures as critical errors and include them in execution info
1015
1128
  console.error(`[CRITICAL] Failed to save output file for execution ${executionId}:`, error);
1016
1129
  executionInfo.message = `Output file save failed: ${error instanceof Error ? error.message : String(error)}`;
1017
1130
  }
1018
1131
  this.executions.set(executionId, executionInfo);
1019
- // detachedプロセス正常終了のコールバック呼び出し
1132
+ // Invoke completion callback for detached process
1020
1133
  if (this.backgroundProcessCallbacks.onComplete) {
1021
1134
  setImmediate(async () => {
1022
1135
  try {
@@ -1029,7 +1142,7 @@ export class ProcessManager {
1029
1142
  }
1030
1143
  }
1031
1144
  catch (callbackError) {
1032
- // コールバックエラーは内部ログに記録のみ
1145
+ // Record callback errors in internal logs only
1033
1146
  // console.error('Detached process completion callback error:', callbackError);
1034
1147
  }
1035
1148
  });
@@ -1043,7 +1156,7 @@ export class ProcessManager {
1043
1156
  executionInfo.execution_time_ms = Date.now() - startTime;
1044
1157
  executionInfo.completed_at = getCurrentTimestamp();
1045
1158
  this.executions.set(executionId, executionInfo);
1046
- // detachedプロセスエラーのコールバック呼び出し
1159
+ // Invoke error callback for detached process
1047
1160
  if (this.backgroundProcessCallbacks.onError) {
1048
1161
  setImmediate(async () => {
1049
1162
  try {
@@ -1056,14 +1169,14 @@ export class ProcessManager {
1056
1169
  }
1057
1170
  }
1058
1171
  catch (callbackError) {
1059
- // コールバックエラーは内部ログに記録のみ
1172
+ // Record callback errors in internal logs only
1060
1173
  // console.error('Detached process error callback error:', callbackError);
1061
1174
  }
1062
1175
  });
1063
1176
  }
1064
1177
  }
1065
1178
  });
1066
- // プロセスをデタッチ
1179
+ // Detach process
1067
1180
  childProcess.unref();
1068
1181
  const resultExecutionInfo = this.executions.get(executionId);
1069
1182
  if (!resultExecutionInfo) {
@@ -1073,7 +1186,7 @@ export class ProcessManager {
1073
1186
  }
1074
1187
  async saveOutputToFile(executionId, stdout, stderr) {
1075
1188
  if (!this.fileManager) {
1076
- // FileManagerが利用できない場合は、従来の方法でファイルを保存
1189
+ // If FileManager is unavailable, save file using legacy method
1077
1190
  const outputFileId = generateId();
1078
1191
  const filePath = path.join(this.outputDir, `${outputFileId}.json`);
1079
1192
  const outputData = {
@@ -1085,27 +1198,27 @@ export class ProcessManager {
1085
1198
  await fs.writeFile(filePath, JSON.stringify(outputData, null, 2), 'utf-8');
1086
1199
  return outputFileId;
1087
1200
  }
1088
- // FileManagerを使用して出力ファイルを作成
1201
+ // Create output file using FileManager
1089
1202
  const combinedOutput = stdout + (stderr ? '\n--- STDERR ---\n' + stderr : '');
1090
1203
  return await this.fileManager.createOutputFile(combinedOutput, executionId);
1091
1204
  }
1092
1205
  /**
1093
- * 出力状態の詳細情報を設定するヘルパー関数
1206
+ * Helper to set detailed output status information
1094
1207
  * Issue #14: Enhanced guidance messages for adaptive mode transitions
1095
- * 改善: outputTruncated の代わりに reason ベースで状態を判定
1208
+ * Improvement: determine status by reason instead of outputTruncated
1096
1209
  */
1097
- setOutputStatus(executionInfo, actuallyTruncated, // 実際に出力が切り捨てられたか
1210
+ setOutputStatus(executionInfo, actuallyTruncated, // whether output was actually truncated
1098
1211
  reason, outputId) {
1099
- // reasonに基づいて出力状態を設定
1100
- const needsGuidance = !!outputId; // output_idがあれば常にガイダンスを提供
1101
- // 後方互換性のため outputTruncated を設定
1212
+ // Set output status based on reason
1213
+ const needsGuidance = !!outputId; // always provide guidance when output_id exists
1214
+ // Set outputTruncated for backward compatibility
1102
1215
  executionInfo.output_truncated =
1103
1216
  actuallyTruncated || reason === 'timeout' || reason === 'background_transition';
1104
- // Issue #14: バックグラウンド移行とタイムアウトは特別扱い
1217
+ // Issue #14: Handle background transitions and timeouts specially
1105
1218
  if (reason === 'background_transition') {
1106
1219
  executionInfo.truncation_reason = reason;
1107
1220
  executionInfo.output_status = {
1108
- complete: false, // バックグラウンド実行中は未完了
1221
+ complete: false, // incomplete while running in background
1109
1222
  reason: reason,
1110
1223
  available_via_output_id: !!outputId,
1111
1224
  recommended_action: outputId ? 'use_read_execution_output' : undefined,
@@ -1136,7 +1249,7 @@ export class ProcessManager {
1136
1249
  if (reason === 'timeout') {
1137
1250
  executionInfo.truncation_reason = reason;
1138
1251
  executionInfo.output_status = {
1139
- complete: false, // タイムアウトは未完了
1252
+ complete: false, // timeout means incomplete
1140
1253
  reason: reason,
1141
1254
  available_via_output_id: !!outputId,
1142
1255
  recommended_action: outputId ? 'use_read_execution_output' : undefined,
@@ -1158,7 +1271,7 @@ export class ProcessManager {
1158
1271
  }
1159
1272
  return;
1160
1273
  }
1161
- // 実際に出力が切り捨てられた場合
1274
+ // When output was actually truncated
1162
1275
  if (actuallyTruncated) {
1163
1276
  executionInfo.truncation_reason = reason;
1164
1277
  executionInfo.output_status = {
@@ -1167,7 +1280,7 @@ export class ProcessManager {
1167
1280
  available_via_output_id: !!outputId,
1168
1281
  recommended_action: outputId ? 'use_read_execution_output' : undefined,
1169
1282
  };
1170
- // 状況に応じたメッセージとアクションの設定
1283
+ // Set message and actions based on situation
1171
1284
  switch (reason) {
1172
1285
  case 'size_limit':
1173
1286
  executionInfo.message = `Output exceeded size limit. ${outputId ? 'Complete output available via output_id.' : 'Output was truncated.'}`;
@@ -1205,7 +1318,7 @@ export class ProcessManager {
1205
1318
  }
1206
1319
  }
1207
1320
  else {
1208
- // 完了した場合(切り捨てなし)
1321
+ // Completed case (no truncation)
1209
1322
  executionInfo.output_status = {
1210
1323
  complete: true,
1211
1324
  available_via_output_id: !!outputId,
@@ -1228,7 +1341,7 @@ export class ProcessManager {
1228
1341
  }
1229
1342
  listExecutions(filter) {
1230
1343
  let executions = Array.from(this.executions.values());
1231
- // フィルタリング
1344
+ // Filtering
1232
1345
  if (filter) {
1233
1346
  if (filter.status) {
1234
1347
  executions = executions.filter((exec) => exec.status === filter.status);
@@ -1238,11 +1351,11 @@ export class ProcessManager {
1238
1351
  executions = executions.filter((exec) => pattern.test(exec.command));
1239
1352
  }
1240
1353
  if (filter.sessionId) {
1241
- // セッション管理は今後実装
1354
+ // Session management will be implemented later
1242
1355
  }
1243
1356
  }
1244
1357
  const total = executions.length;
1245
- // ページネーション
1358
+ // Pagination
1246
1359
  if (filter?.offset || filter?.limit) {
1247
1360
  const offset = filter.offset || 0;
1248
1361
  const limit = filter.limit || 50;
@@ -1256,17 +1369,17 @@ export class ProcessManager {
1256
1369
  throw new ResourceNotFoundError('process', processId.toString());
1257
1370
  }
1258
1371
  try {
1259
- // プロセスを終了
1372
+ // Terminate process
1260
1373
  const signalName = signal === 'KILL' ? 'SIGKILL' : `SIG${signal}`;
1261
1374
  const killed = childProcess.kill(signalName);
1262
1375
  if (!killed && force && signal !== 'KILL') {
1263
- // 強制終了
1376
+ // Force kill
1264
1377
  childProcess.kill('SIGKILL');
1265
1378
  }
1266
- // プロセスが終了するまで待機
1379
+ // Wait until process exits
1267
1380
  await new Promise((resolve) => {
1268
1381
  childProcess.on('close', () => resolve());
1269
- setTimeout(() => resolve(), 5000); // 5秒でタイムアウト
1382
+ setTimeout(() => resolve(), 5000); // timeout after 5 seconds
1270
1383
  });
1271
1384
  this.processes.delete(processId);
1272
1385
  return {
@@ -1287,7 +1400,7 @@ export class ProcessManager {
1287
1400
  listProcesses() {
1288
1401
  const processes = [];
1289
1402
  for (const [pid] of this.processes) {
1290
- // 対応する実行情報を検索
1403
+ // Find corresponding execution info
1291
1404
  const execution = Array.from(this.executions.values()).find((exec) => exec.process_id === pid);
1292
1405
  if (execution) {
1293
1406
  const processInfo = {
@@ -1315,7 +1428,7 @@ export class ProcessManager {
1315
1428
  return processes;
1316
1429
  }
1317
1430
  cleanup() {
1318
- // 実行中のプロセスを全て終了
1431
+ // Terminate all running processes
1319
1432
  for (const [, childProcess] of this.processes) {
1320
1433
  try {
1321
1434
  childProcess.kill('SIGTERM');
@@ -1326,17 +1439,17 @@ export class ProcessManager {
1326
1439
  }, 5000);
1327
1440
  }
1328
1441
  catch (error) {
1329
- // エラーログを内部ログに記録(標準出力を避ける)
1442
+ // Record error in internal log (avoid stdout)
1330
1443
  // console.error(`Failed to cleanup process ${pid}:`, error);
1331
1444
  }
1332
1445
  }
1333
1446
  this.processes.clear();
1334
1447
  this.executions.clear();
1335
1448
  }
1336
- // ワーキングディレクトリ管理
1449
+ // Working directory management
1337
1450
  setDefaultWorkingDirectory(workingDirectory) {
1338
1451
  const previousWorkdir = this.defaultWorkingDirectory;
1339
- // ディレクトリの検証
1452
+ // Validate directory
1340
1453
  if (!this.isAllowedWorkingDirectory(workingDirectory)) {
1341
1454
  throw new Error(`Working directory not allowed: ${workingDirectory}`);
1342
1455
  }
@@ -1355,7 +1468,7 @@ export class ProcessManager {
1355
1468
  return [...this.allowedWorkingDirectories];
1356
1469
  }
1357
1470
  isAllowedWorkingDirectory(workingDirectory) {
1358
- // パスの正規化を行って比較
1471
+ // Compare using normalized paths
1359
1472
  const normalizedPath = path.resolve(workingDirectory);
1360
1473
  return this.allowedWorkingDirectories.some((allowedDir) => {
1361
1474
  const normalizedAllowed = path.resolve(allowedDir);