@pixelbyte-software/pixcode 1.49.7 → 1.49.9

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.
@@ -15,6 +15,8 @@ const DEFAULT_PORT = 8642;
15
15
  const PORT_SCAN_LIMIT = 80;
16
16
  const STARTUP_TIMEOUT_MS = 30000;
17
17
  const FETCH_TIMEOUT_MS = 5000;
18
+ const RUN_TIMEOUT_MS = 120000;
19
+ const RUN_POLL_INTERVAL_MS = 1000;
18
20
  const LOG_LIMIT = 800;
19
21
 
20
22
  const gateways = new Map();
@@ -47,6 +49,10 @@ function makeApiServerKey() {
47
49
  return `pixcode-hermes-${randomBytes(24).toString('hex')}`;
48
50
  }
49
51
 
52
+ function sleep(ms) {
53
+ return new Promise((resolve) => setTimeout(resolve, ms));
54
+ }
55
+
50
56
  export function buildHermesGatewayEnv(baseEnv = process.env, options = {}) {
51
57
  const host = options.host || DEFAULT_HOST;
52
58
  const port = String(options.port || DEFAULT_PORT);
@@ -124,13 +130,127 @@ async function callGateway(gateway, endpoint, options = {}) {
124
130
  });
125
131
  }
126
132
 
133
+ function extractRunId(body) {
134
+ if (!body || typeof body !== 'object') return null;
135
+ return body.run_id || body.runId || body.id || body.run?.id || null;
136
+ }
137
+
138
+ function extractRunStatus(body) {
139
+ if (!body || typeof body !== 'object') return null;
140
+ return body.status || body.state || body.run?.status || body.run?.state || null;
141
+ }
142
+
143
+ function extractTextFromValue(value) {
144
+ if (typeof value === 'string') return value;
145
+ if (!value) return null;
146
+
147
+ if (Array.isArray(value)) {
148
+ return value
149
+ .map(extractTextFromValue)
150
+ .filter(Boolean)
151
+ .join('\n')
152
+ .trim() || null;
153
+ }
154
+
155
+ if (typeof value === 'object') {
156
+ for (const key of ['text', 'content', 'message', 'output', 'response', 'result', 'final']) {
157
+ const text = extractTextFromValue(value[key]);
158
+ if (text) return text;
159
+ }
160
+ }
161
+
162
+ return null;
163
+ }
164
+
165
+ function extractRunOutput(body) {
166
+ if (!body || typeof body !== 'object') return null;
167
+
168
+ for (const key of ['output_text', 'output', 'response', 'result', 'message', 'messages', 'events', 'final']) {
169
+ const text = extractTextFromValue(body[key]);
170
+ if (text) return text;
171
+ }
172
+
173
+ return null;
174
+ }
175
+
176
+ function extractChatCompletionOutput(body) {
177
+ if (!body || typeof body !== 'object') return null;
178
+ const choices = Array.isArray(body.choices) ? body.choices : [];
179
+ for (const choice of choices) {
180
+ const text = extractTextFromValue(choice?.message?.content)
181
+ || extractTextFromValue(choice?.delta?.content)
182
+ || extractTextFromValue(choice?.text);
183
+ if (text) return text;
184
+ }
185
+ return extractTextFromValue(body.output_text)
186
+ || extractTextFromValue(body.output)
187
+ || extractTextFromValue(body.message)
188
+ || extractTextFromValue(body.response)
189
+ || null;
190
+ }
191
+
192
+ function recentGatewayLogText(gateway) {
193
+ if (!gateway?.logs?.length) return '';
194
+ return gateway.logs
195
+ .slice(-16)
196
+ .map((entry) => String(entry.chunk || '').trim())
197
+ .filter(Boolean)
198
+ .join('\n')
199
+ .trim();
200
+ }
201
+
202
+ function gatewayExitMessage(gateway, fallback = 'Hermes gateway is not running.') {
203
+ if (!gateway) return fallback;
204
+ const exit = gateway.exitSignal
205
+ ? `Hermes gateway exited with signal ${gateway.exitSignal}.`
206
+ : `Hermes gateway exited with code ${gateway.exitCode ?? 'unknown'}.`;
207
+ const logs = recentGatewayLogText(gateway);
208
+ return logs ? `${exit}\n${logs}` : (gateway.error || exit);
209
+ }
210
+
211
+ function makeRunRequest(options) {
212
+ const input = String(options.input || '').trim();
213
+ return {
214
+ session_id: options.sessionId || `pixcode-hermes-chat-${Date.now()}-${randomBytes(4).toString('hex')}`,
215
+ input,
216
+ instructions: options.instructions || [
217
+ 'You are Hermes Agent running inside Pixcode.',
218
+ 'Use Pixcode MCP tools when they help inspect projects, launch CLIs, or perform workspace actions.',
219
+ 'Keep answers concise and include concrete next steps when work is blocked.',
220
+ ].join(' '),
221
+ };
222
+ }
223
+
224
+ function makeChatCompletionRequest(options) {
225
+ const input = String(options.input || '').trim();
226
+ const messages = Array.isArray(options.messages) ? options.messages : [
227
+ {
228
+ role: 'system',
229
+ content: options.instructions || [
230
+ 'You are Hermes Agent running inside Pixcode.',
231
+ 'Use Pixcode MCP tools when they help inspect projects, launch CLIs, or perform workspace actions.',
232
+ 'Keep answers concise and include concrete next steps when work is blocked.',
233
+ ].join(' '),
234
+ },
235
+ {
236
+ role: 'user',
237
+ content: input,
238
+ },
239
+ ];
240
+ return {
241
+ model: options.model || 'hermes-agent',
242
+ messages,
243
+ stream: false,
244
+ };
245
+ }
246
+
127
247
  async function waitForGatewayReady(gateway) {
128
248
  const started = Date.now();
129
249
  let lastError = null;
130
250
 
131
251
  while (Date.now() - started < STARTUP_TIMEOUT_MS) {
132
252
  if (!isGatewayRunning(gateway)) {
133
- throw new Error(gateway.error || `Hermes gateway exited with code ${gateway.exitCode ?? 'unknown'}.`);
253
+ throw new Error(gatewayExitMessage(gateway));
134
254
  }
135
255
 
136
256
  try {
@@ -382,6 +502,130 @@ export async function probeHermesGateway(projectPath, options = {}) {
382
502
  return result;
383
503
  }
384
504
 
505
+ export async function runHermesGatewayPrompt(projectPath, options = {}) {
506
+ const gateway = projectPath
507
+ ? gateways.get(normalizeProjectPath(projectPath))
508
+ : Array.from(gateways.values()).find(isGatewayRunning);
509
+
510
+ if (!isGatewayRunning(gateway)) {
511
+ throw new Error('Hermes gateway is not running.');
512
+ }
513
+
514
+ const input = String(options.input || '').trim();
515
+ if (!input) {
516
+ throw new Error('Hermes prompt is required.');
517
+ }
518
+
519
+ const chatRequest = makeChatCompletionRequest({ ...options, input });
520
+ const chat = await callGateway(gateway, '/v1/chat/completions', {
521
+ method: 'POST',
522
+ body: JSON.stringify(chatRequest),
523
+ timeoutMs: options.chatTimeoutMs || options.timeoutMs || RUN_TIMEOUT_MS,
524
+ }).catch((error) => {
525
+ if (!isGatewayRunning(gateway)) {
526
+ throw new Error(gatewayExitMessage(gateway));
527
+ }
528
+ throw error;
529
+ });
530
+
531
+ if (!isGatewayRunning(gateway)) {
532
+ throw new Error(gatewayExitMessage(gateway));
533
+ }
534
+
535
+ if (chat.ok) {
536
+ const message = extractChatCompletionOutput(chat.body);
537
+ return {
538
+ ok: true,
539
+ projectPath: gateway.projectPath,
540
+ baseUrl: gateway.baseUrl,
541
+ sessionId: options.sessionId || null,
542
+ runId: null,
543
+ status: 'completed',
544
+ message,
545
+ raw: chat.body,
546
+ transport: 'chat.completions',
547
+ };
548
+ }
549
+
550
+ if (chat.status && chat.status !== 404 && chat.status !== 405) {
551
+ throw new Error(`Hermes /v1/chat/completions failed with HTTP ${chat.status}: ${JSON.stringify(chat.body)}`);
552
+ }
553
+
554
+ const request = makeRunRequest({ ...options, input });
555
+ const create = await callGateway(gateway, '/v1/runs', {
556
+ method: 'POST',
557
+ body: JSON.stringify(request),
558
+ timeoutMs: options.createTimeoutMs || 15000,
559
+ }).catch((error) => {
560
+ if (!isGatewayRunning(gateway)) {
561
+ throw new Error(gatewayExitMessage(gateway));
562
+ }
563
+ throw error;
564
+ });
565
+
566
+ if (!isGatewayRunning(gateway)) {
567
+ throw new Error(gatewayExitMessage(gateway));
568
+ }
569
+
570
+ if (!create.ok) {
571
+ throw new Error(`Hermes /v1/runs failed with HTTP ${create.status}: ${JSON.stringify(create.body)}`);
572
+ }
573
+
574
+ const runId = extractRunId(create.body);
575
+ const initialStatus = extractRunStatus(create.body);
576
+ if (!runId) {
577
+ return {
578
+ ok: true,
579
+ projectPath: gateway.projectPath,
580
+ baseUrl: gateway.baseUrl,
581
+ sessionId: request.session_id,
582
+ runId: null,
583
+ status: initialStatus || 'completed',
584
+ message: extractRunOutput(create.body),
585
+ raw: create.body,
586
+ transport: 'runs',
587
+ };
588
+ }
589
+
590
+ const terminalStatuses = new Set(['completed', 'failed', 'cancelled', 'canceled']);
591
+ const started = Date.now();
592
+ let latest = create.body;
593
+ let status = initialStatus || 'queued';
594
+
595
+ while (!terminalStatuses.has(String(status)) && Date.now() - started < (options.timeoutMs || RUN_TIMEOUT_MS)) {
596
+ await sleep(options.pollIntervalMs || RUN_POLL_INTERVAL_MS);
597
+ const poll = await callGateway(gateway, `/v1/runs/${encodeURIComponent(runId)}`, {
598
+ timeoutMs: options.pollTimeoutMs || 15000,
599
+ });
600
+ if (!poll.ok) {
601
+ throw new Error(`Hermes /v1/runs/${runId} failed with HTTP ${poll.status}: ${JSON.stringify(poll.body)}`);
602
+ }
603
+ if (!isGatewayRunning(gateway)) {
604
+ throw new Error(gatewayExitMessage(gateway));
605
+ }
606
+ latest = poll.body;
607
+ status = extractRunStatus(latest) || status;
608
+ }
609
+
610
+ if (!terminalStatuses.has(String(status))) {
611
+ throw new Error(`Hermes run did not finish within ${Math.round((options.timeoutMs || RUN_TIMEOUT_MS) / 1000)}s: ${runId}`);
612
+ }
613
+
614
+ const message = extractRunOutput(latest);
615
+ return {
616
+ ok: status === 'completed',
617
+ projectPath: gateway.projectPath,
618
+ baseUrl: gateway.baseUrl,
619
+ sessionId: request.session_id,
620
+ runId,
621
+ status,
622
+ message,
623
+ error: status === 'completed' ? null : extractTextFromValue(latest?.error) || message || 'Hermes run failed.',
624
+ raw: latest,
625
+ transport: 'runs',
626
+ };
627
+ }
628
+
385
629
  export function stopHermesGateway(projectPath) {
386
630
  const targets = projectPath
387
631
  ? [gateways.get(normalizeProjectPath(projectPath))].filter(Boolean)