@pixelbyte-software/pixcode 1.48.6 → 1.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/server/index.js CHANGED
@@ -32,6 +32,10 @@ const SERVER_VERSION = (() => {
32
32
  return '0.0.0';
33
33
  }
34
34
  })();
35
+ const HERMES_SHELL_COMMANDS = new Set([
36
+ 'pixcode:hermes:start',
37
+ 'pixcode:hermes:install',
38
+ ]);
35
39
  const DAEMON_COMMAND_CONTEXT = {
36
40
  appRoot: APP_ROOT,
37
41
  cliEntry: path.join(APP_ROOT, 'server', 'cli.js'),
@@ -105,7 +109,7 @@ import {
105
109
  } from './services/provider-credentials.js';
106
110
  import { primeCliBinPath } from './services/install-jobs.js';
107
111
  import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
108
- import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
112
+ import { initializeDatabase, sessionNamesDb, applyCustomSessionNames, apiKeysDb } from './database/db.js';
109
113
  import { setNotificationWebSocketServer } from './services/notification-orchestrator.js';
110
114
  import { configureWebPush } from './services/vapid-keys.js';
111
115
  import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
@@ -323,6 +327,174 @@ function killProviderPtySessions(projectPath, provider) {
323
327
  return killed;
324
328
  }
325
329
 
330
+ function shellQuotePosix(value) {
331
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
332
+ }
333
+
334
+ function shellQuotePowerShell(value) {
335
+ return `'${String(value).replace(/'/g, "''")}'`;
336
+ }
337
+
338
+ function normalizeShellPermissionMode(value) {
339
+ return typeof value === 'string' ? value.trim() : '';
340
+ }
341
+
342
+ function shouldBypassShellPermissions(permissionMode, skipPermissions) {
343
+ return Boolean(skipPermissions) || permissionMode === 'bypassPermissions' || permissionMode === 'acceptEdits' || permissionMode === 'yolo';
344
+ }
345
+
346
+ function buildProviderShellPermissionFlags(provider, permissionMode, skipPermissions) {
347
+ const mode = normalizeShellPermissionMode(permissionMode);
348
+ const bypass = shouldBypassShellPermissions(mode, skipPermissions);
349
+
350
+ if (provider === 'codex') {
351
+ if (mode === 'bypassPermissions' || mode === 'yolo') {
352
+ return ['--dangerously-bypass-approvals-and-sandbox'];
353
+ }
354
+ if (mode === 'acceptEdits' || mode === 'auto_edit' || bypass) {
355
+ return ['--sandbox', 'workspace-write', '--ask-for-approval', 'never'];
356
+ }
357
+ return [];
358
+ }
359
+
360
+ if (provider === 'gemini' || provider === 'qwen') {
361
+ if (bypass) {
362
+ return ['--yolo'];
363
+ }
364
+ if (mode === 'auto_edit') {
365
+ return ['--approval-mode', 'auto_edit'];
366
+ }
367
+ if (mode === 'plan') {
368
+ return ['--approval-mode', 'plan'];
369
+ }
370
+ return [];
371
+ }
372
+
373
+ if (provider === 'cursor') {
374
+ return bypass ? ['-f'] : [];
375
+ }
376
+
377
+ if (provider === 'opencode') {
378
+ if (mode === 'plan') {
379
+ return ['--agent', 'plan'];
380
+ }
381
+ return bypass ? ['--dangerously-skip-permissions'] : [];
382
+ }
383
+
384
+ if (provider === 'claude') {
385
+ return bypass ? ['--dangerously-skip-permissions'] : [];
386
+ }
387
+
388
+ return [];
389
+ }
390
+
391
+ function buildProviderShellCommand(command, permissionFlags = []) {
392
+ const flags = Array.isArray(permissionFlags) ? permissionFlags.filter(Boolean) : [];
393
+ return flags.length > 0 ? `${command} ${flags.join(' ')}` : command;
394
+ }
395
+
396
+ function resolvePublicBaseUrl(request) {
397
+ const headers = request?.headers || {};
398
+ const forwardedProto = String(headers['x-forwarded-proto'] || '').split(',')[0].trim();
399
+ const proto = forwardedProto || (request?.socket?.encrypted ? 'https' : 'http');
400
+ const host = headers['x-forwarded-host'] || headers.host || `127.0.0.1:${process.env.SERVER_PORT || process.env.PORT || '3001'}`;
401
+ return `${proto}://${String(host).split(',')[0].trim()}`;
402
+ }
403
+
404
+ function getOrCreateHermesApiKey(userId) {
405
+ if (!userId) return null;
406
+
407
+ const existing = apiKeysDb
408
+ .getApiKeys(userId)
409
+ .find((key) => key.key_name === 'Hermes Agent MCP' && key.is_active);
410
+ if (existing?.api_key) {
411
+ return existing.api_key;
412
+ }
413
+
414
+ return apiKeysDb.createApiKey(userId, 'Hermes Agent MCP', [
415
+ 'hermes:mcp',
416
+ 'projects:read',
417
+ 'providers:read',
418
+ 'terminal:launch',
419
+ ]).apiKey;
420
+ }
421
+
422
+ function buildHermesShellCommand(kind, env) {
423
+ const configureScript = path.join(APP_ROOT, 'scripts', 'hermes', 'configure-pixcode-mcp.mjs');
424
+ const isWindows = os.platform() === 'win32';
425
+ const quote = isWindows ? shellQuotePowerShell : shellQuotePosix;
426
+ const configure = `node ${quote(configureScript)}`;
427
+
428
+ if (isWindows) {
429
+ const setEnv = [
430
+ `$env:PIXCODE_BASE_URL=${quote(env.PIXCODE_BASE_URL)}`,
431
+ `$env:PIXCODE_API_KEY=${quote(env.PIXCODE_API_KEY)}`,
432
+ `$env:PIXCODE_APP_ROOT=${quote(env.PIXCODE_APP_ROOT)}`,
433
+ ].join('; ');
434
+ const resolveHermesCommand = [
435
+ 'function Resolve-HermesCommand {',
436
+ '$cmd = Get-Command hermes -ErrorAction SilentlyContinue;',
437
+ 'if ($cmd) { return $cmd.Source; }',
438
+ '$candidates = @(',
439
+ '$env:HERMES_CLI_PATH,',
440
+ '(Join-Path $env:LOCALAPPDATA "hermes\\hermes-agent\\venv\\Scripts\\hermes.exe"),',
441
+ '(Join-Path $env:LOCALAPPDATA "hermes\\hermes-agent\\.venv\\Scripts\\hermes.exe"),',
442
+ '(Join-Path $env:LOCALAPPDATA "hermes\\hermes-agent\\hermes.exe")',
443
+ ');',
444
+ 'foreach ($candidate in $candidates) { if ($candidate -and (Test-Path $candidate)) { return $candidate; } }',
445
+ 'return $null;',
446
+ '}',
447
+ ].join(' ');
448
+ const installHermesIfMissing = [
449
+ 'function Install-HermesIfMissing {',
450
+ '$script:HermesCmd = Resolve-HermesCommand;',
451
+ 'if ($script:HermesCmd) { Write-Host "Hermes already installed:"; & $script:HermesCmd --version; return; }',
452
+ '$installer = Join-Path $env:TEMP "pixcode-hermes-install.ps1";',
453
+ 'Invoke-WebRequest -Uri "https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1" -UseBasicParsing -OutFile $installer;',
454
+ '& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installer -SkipSetup -Branch main;',
455
+ 'if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE; }',
456
+ '$env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + $env:Path;',
457
+ '$script:HermesCmd = Resolve-HermesCommand;',
458
+ 'if (-not $script:HermesCmd) { throw "Hermes installed, but the hermes command could not be found. Restart Pixcode or add Hermes to PATH."; }',
459
+ '}',
460
+ ].join(' ');
461
+ if (kind === 'pixcode:hermes:install') {
462
+ return `${setEnv}; ${resolveHermesCommand}; ${installHermesIfMissing}; Install-HermesIfMissing; ${configure}`;
463
+ }
464
+ return `${setEnv}; ${resolveHermesCommand}; ${installHermesIfMissing}; Install-HermesIfMissing; ${configure}; & $script:HermesCmd chat --toolsets "hermes-cli,mcp-pixcode"`;
465
+ }
466
+
467
+ const setEnv = [
468
+ `PIXCODE_BASE_URL=${quote(env.PIXCODE_BASE_URL)}`,
469
+ `PIXCODE_API_KEY=${quote(env.PIXCODE_API_KEY)}`,
470
+ `PIXCODE_APP_ROOT=${quote(env.PIXCODE_APP_ROOT)}`,
471
+ ].join(' ');
472
+ const resolveHermesCommand = [
473
+ 'resolveHermesCommand() {',
474
+ 'if command -v hermes >/dev/null 2>&1; then command -v hermes; return 0; fi;',
475
+ 'if [ -n "${HERMES_CLI_PATH:-}" ] && [ -x "$HERMES_CLI_PATH" ]; then printf "%s\\n" "$HERMES_CLI_PATH"; return 0; fi;',
476
+ 'if [ -x "$HOME/.local/bin/hermes" ]; then printf "%s\\n" "$HOME/.local/bin/hermes"; return 0; fi;',
477
+ 'if [ -x "$HOME/.hermes/hermes-agent/venv/bin/hermes" ]; then printf "%s\\n" "$HOME/.hermes/hermes-agent/venv/bin/hermes"; return 0; fi;',
478
+ 'if [ -x "/usr/local/bin/hermes" ]; then printf "%s\\n" "/usr/local/bin/hermes"; return 0; fi;',
479
+ 'return 1;',
480
+ '}',
481
+ ].join(' ');
482
+ const install = 'curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash';
483
+ const installHermesIfMissing = [
484
+ 'installHermesIfMissing() {',
485
+ 'HERMES_CMD="$(resolveHermesCommand 2>/dev/null || true)";',
486
+ 'if [ -n "$HERMES_CMD" ]; then echo "Hermes already installed:"; "$HERMES_CMD" --version 2>/dev/null || true; return 0; fi;',
487
+ `${install};`,
488
+ 'HERMES_CMD="$(resolveHermesCommand 2>/dev/null || true)";',
489
+ 'if [ -z "$HERMES_CMD" ]; then echo "Hermes installed, but the hermes command could not be found. Reload PATH and retry." >&2; exit 127; fi;',
490
+ '}',
491
+ ].join(' ');
492
+ if (kind === 'pixcode:hermes:install') {
493
+ return `${setEnv} sh -lc ${quote(`${resolveHermesCommand} ${installHermesIfMissing} installHermesIfMissing && node ${shellQuotePosix(configureScript)}`)}`;
494
+ }
495
+ return `${setEnv} sh -lc ${quote(`${resolveHermesCommand} ${installHermesIfMissing} installHermesIfMissing && node ${shellQuotePosix(configureScript)} && "$HERMES_CMD" chat --toolsets "hermes-cli,mcp-pixcode"`)}`;
496
+ }
497
+
326
498
  // Single WebSocket server that handles both paths
327
499
  const wss = new WebSocketServer({
328
500
  server,
@@ -1851,7 +2023,7 @@ wss.on('connection', (ws, request) => {
1851
2023
  const pathname = urlObj.pathname;
1852
2024
 
1853
2025
  if (pathname === '/shell') {
1854
- handleShellConnection(ws);
2026
+ handleShellConnection(ws, request);
1855
2027
  } else if (pathname === '/ws') {
1856
2028
  handleChatConnection(ws, request);
1857
2029
  } else if (pathname.startsWith('/plugin-ws/')) {
@@ -2060,7 +2232,7 @@ function handleChatConnection(ws, request) {
2060
2232
  }
2061
2233
 
2062
2234
  // Handle shell WebSocket connections
2063
- function handleShellConnection(ws) {
2235
+ function handleShellConnection(ws, request) {
2064
2236
  console.log('🐚 Shell client connected');
2065
2237
  let shellProcess = null;
2066
2238
  let ptySessionKey = null;
@@ -2090,9 +2262,25 @@ function handleShellConnection(ws) {
2090
2262
  const sessionId = data.sessionId;
2091
2263
  const hasSession = data.hasSession;
2092
2264
  const provider = data.provider || 'claude';
2093
- const initialCommand = data.initialCommand;
2265
+ let initialCommand = data.initialCommand;
2266
+ const hermesCommand = HERMES_SHELL_COMMANDS.has(initialCommand) ? initialCommand : null;
2267
+ if (hermesCommand) {
2268
+ const apiKey = getOrCreateHermesApiKey(request?.user?.id);
2269
+ if (!apiKey) {
2270
+ ws.send(JSON.stringify({ type: 'error', message: 'Hermes MCP could not create a Pixcode API key for this user.' }));
2271
+ return;
2272
+ }
2273
+ initialCommand = buildHermesShellCommand(hermesCommand, {
2274
+ PIXCODE_BASE_URL: resolvePublicBaseUrl(request),
2275
+ PIXCODE_API_KEY: apiKey,
2276
+ PIXCODE_APP_ROOT: APP_ROOT,
2277
+ });
2278
+ }
2094
2279
  const isPlainShell = data.isPlainShell || (!!initialCommand && !hasSession) || provider === 'plain-shell';
2095
2280
  const forceNewSession = Boolean(data.forceNewSession);
2281
+ const shellPermissionMode = normalizeShellPermissionMode(data.permissionMode);
2282
+ const shellSkipPermissions = Boolean(data.skipPermissions);
2283
+ const shellPermissionFlags = buildProviderShellPermissionFlags(provider, shellPermissionMode, shellSkipPermissions);
2096
2284
  urlDetectionBuffer = '';
2097
2285
  announcedAuthUrls.clear();
2098
2286
 
@@ -2173,7 +2361,7 @@ function handleShellConnection(ws) {
2173
2361
  console.log('📋 Session info:', hasSession ? `Resume session ${sessionId}` : (isPlainShell ? 'Plain shell mode' : 'New session'));
2174
2362
  console.log('🤖 Provider:', isPlainShell ? 'plain-shell' : provider);
2175
2363
  if (initialCommand) {
2176
- console.log('⚡ Initial command:', initialCommand);
2364
+ console.log('⚡ Initial command:', hermesCommand ? hermesCommand : initialCommand);
2177
2365
  }
2178
2366
 
2179
2367
  // First send a welcome message
@@ -2223,25 +2411,27 @@ function handleShellConnection(ws) {
2223
2411
  // Plain shell mode without an initial command must stay interactive.
2224
2412
  shellCommand = initialCommand || null;
2225
2413
  } else if (provider === 'cursor') {
2414
+ const command = buildProviderShellCommand('cursor-agent', shellPermissionFlags);
2226
2415
  if (hasSession && sessionId) {
2227
- shellCommand = `cursor-agent --resume="${sessionId}"`;
2416
+ shellCommand = `${command} --resume="${sessionId}"`;
2228
2417
  } else {
2229
- shellCommand = 'cursor-agent';
2418
+ shellCommand = command;
2230
2419
  }
2231
2420
  } else if (provider === 'codex') {
2232
2421
  // Use codex command; attempt to resume and fall back to a new session when the resume fails.
2422
+ const command = buildProviderShellCommand('codex', shellPermissionFlags);
2233
2423
  if (hasSession && sessionId) {
2234
2424
  if (os.platform() === 'win32') {
2235
2425
  // PowerShell syntax for fallback
2236
- shellCommand = `codex resume "${sessionId}"; if ($LASTEXITCODE -ne 0) { codex }`;
2426
+ shellCommand = `${command} resume "${sessionId}"; if ($LASTEXITCODE -ne 0) { ${command} }`;
2237
2427
  } else {
2238
- shellCommand = `codex resume "${sessionId}" || codex`;
2428
+ shellCommand = `${command} resume "${sessionId}" || ${command}`;
2239
2429
  }
2240
2430
  } else {
2241
- shellCommand = 'codex';
2431
+ shellCommand = command;
2242
2432
  }
2243
2433
  } else if (provider === 'gemini') {
2244
- const command = initialCommand || 'gemini';
2434
+ const command = buildProviderShellCommand(initialCommand || 'gemini', shellPermissionFlags);
2245
2435
  let resumeId = sessionId;
2246
2436
  if (hasSession && sessionId) {
2247
2437
  try {
@@ -2270,7 +2460,7 @@ function handleShellConnection(ws) {
2270
2460
  // Qwen Code shares Gemini CLI's --resume semantics (it's a fork),
2271
2461
  // so the resume path resolves the backend-tracked cliSessionId the
2272
2462
  // same way. Falls back to a fresh session when the ID can't be found.
2273
- const command = initialCommand || 'qwen';
2463
+ const command = buildProviderShellCommand(initialCommand || 'qwen', shellPermissionFlags);
2274
2464
  let resumeId = sessionId;
2275
2465
  if (hasSession && sessionId) {
2276
2466
  try {
@@ -2298,7 +2488,7 @@ function handleShellConnection(ws) {
2298
2488
  // we pass them straight through without a cliSessionId
2299
2489
  // mapping layer — OpenCode doesn't renumber IDs the way
2300
2490
  // Gemini does.
2301
- const command = initialCommand || 'opencode';
2491
+ const command = buildProviderShellCommand(initialCommand || 'opencode', shellPermissionFlags);
2302
2492
  if (hasSession && sessionId && safeSessionIdPattern.test(sessionId)) {
2303
2493
  shellCommand = `${command} --session "${sessionId}"`;
2304
2494
  } else {
@@ -2306,19 +2496,19 @@ function handleShellConnection(ws) {
2306
2496
  }
2307
2497
  } else {
2308
2498
  // Claude (default provider)
2309
- const command = initialCommand || 'claude';
2499
+ const command = buildProviderShellCommand(initialCommand || 'claude', shellPermissionFlags);
2310
2500
  if (hasSession && sessionId) {
2311
2501
  if (os.platform() === 'win32') {
2312
- shellCommand = `claude --resume "${sessionId}"; if ($LASTEXITCODE -ne 0) { claude }`;
2502
+ shellCommand = `${command} --resume "${sessionId}"; if ($LASTEXITCODE -ne 0) { ${command} }`;
2313
2503
  } else {
2314
- shellCommand = `claude --resume "${sessionId}" || claude`;
2504
+ shellCommand = `${command} --resume "${sessionId}" || ${command}`;
2315
2505
  }
2316
2506
  } else {
2317
2507
  shellCommand = command;
2318
2508
  }
2319
2509
  }
2320
2510
 
2321
- console.log('🔧 Executing shell command:', shellCommand || 'interactive shell');
2511
+ console.log('🔧 Executing shell command:', hermesCommand ? hermesCommand : (shellCommand || 'interactive shell'));
2322
2512
 
2323
2513
  // Use appropriate shell based on platform
2324
2514
  const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
@@ -1,10 +1,85 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
1
3
  import os from 'node:os';
4
+ import path from 'node:path';
2
5
 
3
6
  import express, { type Router } from 'express';
4
7
 
5
8
  import { adapterRegistry } from '@/modules/orchestration/a2a/adapter-registry.js';
6
9
  import { a2aTaskStore as hermesTaskStore } from '@/modules/orchestration/a2a/task-store.js';
7
10
 
11
+ const HERMES_TERMINAL_LAUNCH_LIMIT = 100;
12
+ const HERMES_TERMINAL_LAUNCH_PROVIDERS = new Set(['claude', 'codex', 'cursor', 'gemini', 'qwen', 'opencode']);
13
+
14
+ type HermesTerminalLaunchEvent = {
15
+ id: number;
16
+ provider: string;
17
+ projectPath: string | null;
18
+ prompt: string | null;
19
+ source: string;
20
+ createdAt: string;
21
+ };
22
+
23
+ let nextHermesTerminalLaunchId = 1;
24
+ const hermesTerminalLaunches: HermesTerminalLaunchEvent[] = [];
25
+
26
+ function hermesCommandCandidates(): string[] {
27
+ const candidates = [process.env.HERMES_CLI_PATH, 'hermes'].filter((candidate): candidate is string => (
28
+ typeof candidate === 'string' && candidate.trim().length > 0
29
+ ));
30
+
31
+ if (process.platform === 'win32') {
32
+ const localAppData = process.env.LOCALAPPDATA;
33
+ if (localAppData) {
34
+ candidates.push(
35
+ path.join(localAppData, 'hermes', 'hermes-agent', 'venv', 'Scripts', 'hermes.exe'),
36
+ path.join(localAppData, 'hermes', 'hermes-agent', '.venv', 'Scripts', 'hermes.exe'),
37
+ path.join(localAppData, 'hermes', 'hermes-agent', 'hermes.exe'),
38
+ );
39
+ }
40
+ } else {
41
+ candidates.push(
42
+ path.join(os.homedir(), '.local', 'bin', 'hermes'),
43
+ path.join(os.homedir(), '.hermes', 'hermes-agent', 'venv', 'bin', 'hermes'),
44
+ '/usr/local/bin/hermes',
45
+ );
46
+ }
47
+
48
+ return [...new Set(candidates)];
49
+ }
50
+
51
+ function readHermesInstallStatus() {
52
+ for (const candidate of hermesCommandCandidates()) {
53
+ const isBareCommand = candidate === 'hermes';
54
+ if (!isBareCommand && !existsSync(candidate)) {
55
+ continue;
56
+ }
57
+
58
+ const result = spawnSync(candidate, ['--version'], {
59
+ encoding: 'utf8',
60
+ stdio: ['ignore', 'pipe', 'pipe'],
61
+ timeout: 5000,
62
+ shell: false,
63
+ });
64
+ if (!result.error && result.status === 0) {
65
+ const version = `${result.stdout || result.stderr || ''}`.trim() || null;
66
+ return {
67
+ installed: true,
68
+ command: candidate,
69
+ version,
70
+ error: null,
71
+ };
72
+ }
73
+ }
74
+
75
+ return {
76
+ installed: false,
77
+ command: null,
78
+ version: null,
79
+ error: 'Hermes Agent CLI is not installed or is not on PATH.',
80
+ };
81
+ }
82
+
8
83
  export function createHermesRouter(): Router {
9
84
  const router = express.Router();
10
85
 
@@ -43,6 +118,10 @@ export function createHermesRouter(): Router {
43
118
  });
44
119
  });
45
120
 
121
+ router.get('/install-status', (_req, res) => {
122
+ res.json(readHermesInstallStatus());
123
+ });
124
+
46
125
  router.get('/agents', (_req, res) => {
47
126
  res.json({
48
127
  agent: 'hermes',
@@ -53,6 +132,46 @@ export function createHermesRouter(): Router {
53
132
  });
54
133
  });
55
134
 
135
+ router.get('/terminal-launches', (req, res) => {
136
+ const after = Number.parseInt(typeof req.query.after === 'string' ? req.query.after : '0', 10);
137
+ const afterId = Number.isFinite(after) ? after : 0;
138
+ res.json({
139
+ events: hermesTerminalLaunches.filter((event) => event.id > afterId),
140
+ });
141
+ });
142
+
143
+ router.post('/terminal-launches', (req, res) => {
144
+ const body = (req.body ?? {}) as Record<string, unknown>;
145
+ const provider = typeof body.provider === 'string' ? body.provider.trim() : '';
146
+ if (!HERMES_TERMINAL_LAUNCH_PROVIDERS.has(provider)) {
147
+ res.status(400).json({ error: { code: 'INVALID_PROVIDER', message: provider || 'provider is required' } });
148
+ return;
149
+ }
150
+
151
+ const projectPath = typeof body.projectPath === 'string' && body.projectPath.trim()
152
+ ? body.projectPath.trim()
153
+ : null;
154
+ const prompt = typeof body.prompt === 'string' && body.prompt.trim()
155
+ ? body.prompt.trim()
156
+ : null;
157
+
158
+ const event: HermesTerminalLaunchEvent = {
159
+ id: nextHermesTerminalLaunchId,
160
+ provider,
161
+ projectPath,
162
+ prompt,
163
+ source: 'hermes-mcp',
164
+ createdAt: new Date().toISOString(),
165
+ };
166
+ nextHermesTerminalLaunchId += 1;
167
+ hermesTerminalLaunches.push(event);
168
+ if (hermesTerminalLaunches.length > HERMES_TERMINAL_LAUNCH_LIMIT) {
169
+ hermesTerminalLaunches.splice(0, hermesTerminalLaunches.length - HERMES_TERMINAL_LAUNCH_LIMIT);
170
+ }
171
+
172
+ res.status(201).json({ event });
173
+ });
174
+
56
175
  router.get('/tasks/:id', (req, res) => {
57
176
  const task = hermesTaskStore.get(req.params.id);
58
177
  if (!task) {
@@ -102,7 +102,13 @@ async function spawnQwen(command, options = {}, ws) {
102
102
  args.push('--model', modelToUse);
103
103
  args.push('--output-format', 'stream-json');
104
104
 
105
- if (settings.skipPermissions || options.skipPermissions || permissionMode === 'yolo') {
105
+ if (
106
+ settings.skipPermissions ||
107
+ options.skipPermissions ||
108
+ permissionMode === 'yolo' ||
109
+ permissionMode === 'bypassPermissions' ||
110
+ permissionMode === 'acceptEdits'
111
+ ) {
106
112
  args.push('--yolo');
107
113
  } else if (permissionMode === 'auto_edit') {
108
114
  args.push('--approval-mode', 'auto_edit');