@pixelbyte-software/pixcode 1.49.9 → 1.49.10

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.
@@ -173,6 +173,29 @@ function extractRunOutput(body) {
173
173
  return null;
174
174
  }
175
175
 
176
+ function extractResponsesOutput(body) {
177
+ if (!body || typeof body !== 'object') return null;
178
+
179
+ const output = Array.isArray(body.output) ? body.output : [];
180
+ for (const item of output) {
181
+ if (!item || typeof item !== 'object') continue;
182
+ if (item.type === 'message' || item.role === 'assistant') {
183
+ const text = extractTextFromValue(item.content);
184
+ if (text) return text;
185
+ }
186
+ const text = extractTextFromValue(item.output_text)
187
+ || extractTextFromValue(item.text)
188
+ || extractTextFromValue(item.message)
189
+ || extractTextFromValue(item.output);
190
+ if (text) return text;
191
+ }
192
+
193
+ return extractTextFromValue(body.output_text)
194
+ || extractTextFromValue(body.message)
195
+ || extractTextFromValue(body.response)
196
+ || null;
197
+ }
198
+
176
199
  function extractChatCompletionOutput(body) {
177
200
  if (!body || typeof body !== 'object') return null;
178
201
  const choices = Array.isArray(body.choices) ? body.choices : [];
@@ -244,6 +267,21 @@ function makeChatCompletionRequest(options) {
244
267
  };
245
268
  }
246
269
 
270
+ function makeResponsesRequest(options) {
271
+ const input = String(options.input || '').trim();
272
+ return {
273
+ model: options.model || 'hermes-agent',
274
+ input,
275
+ instructions: options.instructions || [
276
+ 'You are Hermes Agent running inside Pixcode.',
277
+ 'Use Pixcode MCP tools when they help inspect projects, launch CLIs, or perform workspace actions.',
278
+ 'Keep answers concise and include concrete next steps when work is blocked.',
279
+ ].join(' '),
280
+ conversation: options.sessionId || undefined,
281
+ store: true,
282
+ };
283
+ }
284
+
247
285
  async function waitForGatewayReady(gateway) {
248
286
  const started = Date.now();
249
287
  let lastError = null;
@@ -377,7 +415,10 @@ export async function ensureHermesGateway(options = {}) {
377
415
  apiServerKey,
378
416
  appRoot,
379
417
  });
380
- const installStatus = readHermesInstallStatus(env);
418
+ const installStatus = readHermesInstallStatus(env, {
419
+ allowSmokeHermes: options.allowSmokeHermes === true,
420
+ repairLaunchers: options.repairLaunchers !== false,
421
+ });
381
422
  if (!installStatus.installed || !installStatus.command) {
382
423
  throw new Error(installStatus.error || 'Hermes Agent CLI is not installed.');
383
424
  }
@@ -403,14 +444,15 @@ export async function ensureHermesGateway(options = {}) {
403
444
 
404
445
  await configurePixcodeMcp({ appRoot, env, gateway });
405
446
 
406
- const child = spawn(installStatus.command, ['gateway'], {
447
+ const gatewayArgs = options.gatewayArgs || ['gateway', 'run', '--replace'];
448
+ const child = spawn(installStatus.command, gatewayArgs, {
407
449
  cwd: projectPath,
408
450
  env,
409
451
  stdio: ['ignore', 'pipe', 'pipe'],
410
452
  windowsHide: true,
411
453
  });
412
454
  gateway.child = child;
413
- appendGatewayLog(gateway, 'meta', `$ ${installStatus.command} gateway\n`);
455
+ appendGatewayLog(gateway, 'meta', `$ ${installStatus.command} ${gatewayArgs.join(' ')}\n`);
414
456
 
415
457
  child.stdout?.on('data', (buf) => appendGatewayLog(gateway, 'stdout', buf.toString()));
416
458
  child.stderr?.on('data', (buf) => appendGatewayLog(gateway, 'stderr', buf.toString()));
@@ -516,6 +558,46 @@ export async function runHermesGatewayPrompt(projectPath, options = {}) {
516
558
  throw new Error('Hermes prompt is required.');
517
559
  }
518
560
 
561
+ const responsesRequest = makeResponsesRequest({ ...options, input });
562
+ const responseRun = await callGateway(gateway, '/v1/responses', {
563
+ method: 'POST',
564
+ body: JSON.stringify(responsesRequest),
565
+ timeoutMs: options.responsesTimeoutMs || options.timeoutMs || RUN_TIMEOUT_MS,
566
+ }).catch((error) => {
567
+ if (!isGatewayRunning(gateway)) {
568
+ throw new Error(gatewayExitMessage(gateway));
569
+ }
570
+ throw error;
571
+ });
572
+
573
+ if (!isGatewayRunning(gateway)) {
574
+ throw new Error(gatewayExitMessage(gateway));
575
+ }
576
+
577
+ if (responseRun.ok) {
578
+ const status = extractRunStatus(responseRun.body) || 'completed';
579
+ const message = extractResponsesOutput(responseRun.body);
580
+ return {
581
+ ok: status === 'completed' || status === 'succeeded',
582
+ projectPath: gateway.projectPath,
583
+ baseUrl: gateway.baseUrl,
584
+ sessionId: options.sessionId || responsesRequest.conversation || null,
585
+ runId: null,
586
+ responseId: responseRun.body?.id || null,
587
+ status,
588
+ message,
589
+ error: (status === 'completed' || status === 'succeeded') ? null : extractTextFromValue(responseRun.body?.error) || message || 'Hermes response failed.',
590
+ raw: responseRun.body,
591
+ transport: 'responses',
592
+ endpoint: '/v1/responses',
593
+ httpStatus: responseRun.status,
594
+ };
595
+ }
596
+
597
+ if (responseRun.status && responseRun.status !== 404 && responseRun.status !== 405) {
598
+ throw new Error(`Hermes /v1/responses failed with HTTP ${responseRun.status}: ${JSON.stringify(responseRun.body)}`);
599
+ }
600
+
519
601
  const chatRequest = makeChatCompletionRequest({ ...options, input });
520
602
  const chat = await callGateway(gateway, '/v1/chat/completions', {
521
603
  method: 'POST',
@@ -544,6 +626,8 @@ export async function runHermesGatewayPrompt(projectPath, options = {}) {
544
626
  message,
545
627
  raw: chat.body,
546
628
  transport: 'chat.completions',
629
+ endpoint: '/v1/chat/completions',
630
+ httpStatus: chat.status,
547
631
  };
548
632
  }
549
633
 
@@ -584,6 +668,8 @@ export async function runHermesGatewayPrompt(projectPath, options = {}) {
584
668
  message: extractRunOutput(create.body),
585
669
  raw: create.body,
586
670
  transport: 'runs',
671
+ endpoint: '/v1/runs',
672
+ httpStatus: create.status,
587
673
  };
588
674
  }
589
675
 
@@ -623,6 +709,8 @@ export async function runHermesGatewayPrompt(projectPath, options = {}) {
623
709
  error: status === 'completed' ? null : extractTextFromValue(latest?.error) || message || 'Hermes run failed.',
624
710
  raw: latest,
625
711
  transport: 'runs',
712
+ endpoint: '/v1/runs',
713
+ httpStatus: create.status,
626
714
  };
627
715
  }
628
716
 
@@ -144,8 +144,40 @@ function runHermesVersion(candidate, env) {
144
144
  }
145
145
  }
146
146
 
147
- export function isUsableHermesCommand(candidate, env = process.env) {
148
- return runHermesVersion(candidate, buildHermesEnv(env)).ok;
147
+ function isHermesSmokeCommandOutput(output) {
148
+ return /Hermes Agent v0\.0\.0\s+smoke/i.test(String(output || ''))
149
+ || /pixcode-hermes-(?:chat-api|smoke)/i.test(String(output || ''));
150
+ }
151
+
152
+ function isExplicitHermesCliPath(candidate, env = process.env) {
153
+ if (!candidate || !env.HERMES_CLI_PATH || !path.isAbsolute(candidate)) return false;
154
+ try {
155
+ return path.resolve(candidate) === path.resolve(env.HERMES_CLI_PATH);
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+
161
+ function isTemporaryHermesLauncher(candidate) {
162
+ if (!candidate || !path.isAbsolute(candidate)) return false;
163
+ const normalized = path.resolve(candidate);
164
+ const tempRoot = path.resolve(os.tmpdir());
165
+ return normalized === tempRoot || normalized.startsWith(`${tempRoot}${path.sep}`);
166
+ }
167
+
168
+ function shouldRepairHermesLauncher(command, env = process.env, options = {}) {
169
+ if (options.repairLaunchers === false) return false;
170
+ if (!command || command === 'hermes') return false;
171
+ if (isExplicitHermesCliPath(command, env)) return false;
172
+ if (isTemporaryHermesLauncher(command)) return false;
173
+ return true;
174
+ }
175
+
176
+ export function isUsableHermesCommand(candidate, env = process.env, options = {}) {
177
+ const result = runHermesVersion(candidate, buildHermesEnv(env));
178
+ if (!result.ok) return false;
179
+ if (!options.allowSmokeHermes && isHermesSmokeCommandOutput(result.output)) return false;
180
+ return true;
149
181
  }
150
182
 
151
183
  function isHermesPythonLauncher(candidate) {
@@ -313,8 +345,9 @@ export function hermesCommandCandidates(env = process.env) {
313
345
  return [...new Set(candidates.filter(Boolean))];
314
346
  }
315
347
 
316
- export function readHermesInstallStatus(env = process.env) {
348
+ export function readHermesInstallStatus(env = process.env, options = {}) {
317
349
  const hermesEnv = buildHermesEnv(env);
350
+ const rejected = [];
318
351
 
319
352
  for (const candidate of hermesCommandCandidates(hermesEnv)) {
320
353
  const isBareCommand = candidate === 'hermes';
@@ -324,8 +357,16 @@ export function readHermesInstallStatus(env = process.env) {
324
357
 
325
358
  const result = runHermesVersion(candidate, hermesEnv);
326
359
  if (result.ok) {
327
- repairHermesCommandLaunchers(candidate, hermesEnv);
328
360
  const version = formatHermesVersionOutput(result.output);
361
+ if (!options.allowSmokeHermes && isHermesSmokeCommandOutput(result.output)) {
362
+ rejected.push(`${candidate} (${version || 'smoke-test Hermes launcher'})`);
363
+ continue;
364
+ }
365
+
366
+ if (shouldRepairHermesLauncher(candidate, hermesEnv, options)) {
367
+ repairHermesCommandLaunchers(candidate, hermesEnv);
368
+ }
369
+
329
370
  return {
330
371
  installed: true,
331
372
  command: candidate,
@@ -339,7 +380,9 @@ export function readHermesInstallStatus(env = process.env) {
339
380
  installed: false,
340
381
  command: null,
341
382
  version: null,
342
- error: 'Hermes Agent CLI is not installed or is not on PATH.',
383
+ error: rejected.length > 0
384
+ ? `Only smoke-test Hermes launchers were found and rejected: ${rejected.join(', ')}. Install or repair Hermes Agent.`
385
+ : 'Hermes Agent CLI is not installed or is not on PATH.',
343
386
  };
344
387
  }
345
388