@pixelbyte-software/pixcode 1.49.10 → 1.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-DVpGrdpT.js → index-81sOpj25.js} +182 -182
- package/dist/assets/index-DMz0zv6T.css +32 -0
- package/dist/hermes-agent.png +0 -0
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +77 -1
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js +40 -7
- package/dist-server/server/modules/orchestration/hermes/hermes.routes.js.map +1 -1
- package/dist-server/server/services/hermes-gateway.js +115 -1
- package/dist-server/server/services/hermes-gateway.js.map +1 -1
- package/dist-server/server/services/hermes-install-jobs.js +9 -1
- package/dist-server/server/services/hermes-install-jobs.js.map +1 -1
- package/package.json +1 -1
- package/scripts/hermes/configure-pixcode-mcp.mjs +37 -1
- package/scripts/hermes/pixcode-mcp-server.mjs +166 -8
- package/scripts/smoke/hermes-api-install.mjs +4 -4
- package/scripts/smoke/hermes-gateway-persistence.mjs +104 -0
- package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +27 -0
- package/scripts/smoke/hermes-rest-chat-live.mjs +4 -0
- package/scripts/smoke/hermes-rest-codex-launch.mjs +11 -4
- package/scripts/smoke/hermes-rest-gateway.mjs +9 -1
- package/scripts/smoke/hermes-settings-commands.mjs +166 -0
- package/scripts/smoke/pixcode-workbench-1-48.mjs +13 -5
- package/server/index.js +94 -1
- package/server/modules/orchestration/hermes/hermes.routes.ts +45 -7
- package/server/services/hermes-gateway.js +128 -1
- package/server/services/hermes-install-jobs.js +11 -1
- package/dist/assets/index-BT6txdBK.css +0 -32
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import net from 'node:net';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -18,6 +19,27 @@ const FETCH_TIMEOUT_MS = 5000;
|
|
|
18
19
|
const RUN_TIMEOUT_MS = 120000;
|
|
19
20
|
const RUN_POLL_INTERVAL_MS = 1000;
|
|
20
21
|
const LOG_LIMIT = 800;
|
|
22
|
+
const PIXCODE_MANAGED_HERMES_ENV_PREFIXES = [
|
|
23
|
+
'API_SERVER_',
|
|
24
|
+
'BLUEBUBBLES_',
|
|
25
|
+
'DINGTALK_',
|
|
26
|
+
'DISCORD_',
|
|
27
|
+
'EMAIL_',
|
|
28
|
+
'FEISHU_',
|
|
29
|
+
'MATTERMOST_',
|
|
30
|
+
'MATRIX_',
|
|
31
|
+
'MSGRAPH_',
|
|
32
|
+
'QQ_',
|
|
33
|
+
'SIGNAL_',
|
|
34
|
+
'SLACK_',
|
|
35
|
+
'SMS_',
|
|
36
|
+
'TELEGRAM_',
|
|
37
|
+
'TWILIO_',
|
|
38
|
+
'WECOM_',
|
|
39
|
+
'WEIXIN_',
|
|
40
|
+
'WHATSAPP_',
|
|
41
|
+
'YUANBAO_',
|
|
42
|
+
];
|
|
21
43
|
|
|
22
44
|
const gateways = new Map();
|
|
23
45
|
|
|
@@ -53,6 +75,96 @@ function sleep(ms) {
|
|
|
53
75
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
54
76
|
}
|
|
55
77
|
|
|
78
|
+
function resolveSourceHermesHome(env = process.env) {
|
|
79
|
+
if (env.HERMES_HOME?.trim()) {
|
|
80
|
+
return path.resolve(env.HERMES_HOME);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const defaultHome = path.join(os.homedir(), '.hermes');
|
|
84
|
+
try {
|
|
85
|
+
const activeProfile = fs.readFileSync(path.join(defaultHome, 'active_profile'), 'utf8').trim();
|
|
86
|
+
if (activeProfile && activeProfile !== 'default' && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(activeProfile)) {
|
|
87
|
+
return path.join(defaultHome, 'profiles', activeProfile);
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// Default Hermes profile is fine when no sticky active profile exists.
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return defaultHome;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveHermesGatewayHome(env = process.env, options = {}) {
|
|
97
|
+
const configured = options.hermesHome || env.PIXCODE_HERMES_GATEWAY_HOME;
|
|
98
|
+
if (configured) {
|
|
99
|
+
return path.resolve(configured);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return path.join(os.homedir(), '.hermes', 'profiles', 'pixcode');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function copyHermesProfileFile(sourceHome, targetHome, fileName, options = {}) {
|
|
106
|
+
const source = path.join(sourceHome, fileName);
|
|
107
|
+
const target = path.join(targetHome, fileName);
|
|
108
|
+
if (!fs.existsSync(source)) return false;
|
|
109
|
+
if (!options.overwrite && fs.existsSync(target)) return false;
|
|
110
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
111
|
+
fs.copyFileSync(source, target);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function shouldStripManagedGatewayEnvLine(line) {
|
|
116
|
+
const match = String(line || '').match(/^\s*(?:export\s+)?([A-Z0-9_]+)\s*=/);
|
|
117
|
+
if (!match) return false;
|
|
118
|
+
return PIXCODE_MANAGED_HERMES_ENV_PREFIXES.some((prefix) => match[1].startsWith(prefix));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function copyHermesProfileEnv(sourceHome, targetHome) {
|
|
122
|
+
const source = path.join(sourceHome, '.env');
|
|
123
|
+
const target = path.join(targetHome, '.env');
|
|
124
|
+
if (!fs.existsSync(source)) return false;
|
|
125
|
+
|
|
126
|
+
const sourceText = fs.readFileSync(source, 'utf8');
|
|
127
|
+
const sanitized = sourceText
|
|
128
|
+
.split(/\r?\n/)
|
|
129
|
+
.filter((line) => !shouldStripManagedGatewayEnvLine(line))
|
|
130
|
+
.join('\n')
|
|
131
|
+
.replace(/\s*$/, '\n');
|
|
132
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
133
|
+
fs.writeFileSync(target, sanitized);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function seedHermesGatewayHome({ sourceHome, targetHome, gateway }) {
|
|
138
|
+
fs.mkdirSync(targetHome, { recursive: true });
|
|
139
|
+
if (path.resolve(sourceHome) === path.resolve(targetHome)) {
|
|
140
|
+
appendGatewayLog(gateway, 'meta', `Using Hermes gateway profile at ${targetHome}\n`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const copied = [];
|
|
145
|
+
for (const file of ['config.yaml', 'SOUL.md']) {
|
|
146
|
+
if (copyHermesProfileFile(sourceHome, targetHome, file, { overwrite: false })) {
|
|
147
|
+
copied.push(file);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (copyHermesProfileEnv(sourceHome, targetHome)) {
|
|
151
|
+
copied.push('.env (without messaging platform credentials)');
|
|
152
|
+
}
|
|
153
|
+
for (const file of ['auth.json']) {
|
|
154
|
+
if (copyHermesProfileFile(sourceHome, targetHome, file, { overwrite: true })) {
|
|
155
|
+
copied.push(file);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
appendGatewayLog(
|
|
160
|
+
gateway,
|
|
161
|
+
'meta',
|
|
162
|
+
copied.length > 0
|
|
163
|
+
? `Seeded Pixcode Hermes gateway profile from ${sourceHome}: ${copied.join(', ')}\n`
|
|
164
|
+
: `Using Pixcode Hermes gateway profile at ${targetHome}\n`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
56
168
|
export function buildHermesGatewayEnv(baseEnv = process.env, options = {}) {
|
|
57
169
|
const host = options.host || DEFAULT_HOST;
|
|
58
170
|
const port = String(options.port || DEFAULT_PORT);
|
|
@@ -345,6 +457,7 @@ function snapshotGateway(gateway) {
|
|
|
345
457
|
running: false,
|
|
346
458
|
projectPath: null,
|
|
347
459
|
baseUrl: null,
|
|
460
|
+
hermesHome: null,
|
|
348
461
|
host: null,
|
|
349
462
|
port: null,
|
|
350
463
|
pid: null,
|
|
@@ -362,6 +475,7 @@ function snapshotGateway(gateway) {
|
|
|
362
475
|
running: isGatewayRunning(gateway),
|
|
363
476
|
projectPath: gateway.projectPath,
|
|
364
477
|
baseUrl: gateway.baseUrl,
|
|
478
|
+
hermesHome: gateway.hermesHome,
|
|
365
479
|
host: gateway.host,
|
|
366
480
|
port: gateway.port,
|
|
367
481
|
pid: gateway.child?.pid ?? null,
|
|
@@ -391,16 +505,24 @@ export async function ensureHermesGateway(options = {}) {
|
|
|
391
505
|
const projectPath = normalizeProjectPath(options.projectPath);
|
|
392
506
|
const existing = gateways.get(projectPath);
|
|
393
507
|
if (isGatewayRunning(existing)) {
|
|
508
|
+
if (options.probeExisting !== true) {
|
|
509
|
+
return {
|
|
510
|
+
...snapshotGateway(existing),
|
|
511
|
+
probe: existing.lastProbe,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
394
515
|
const probe = await probeHermesGateway(projectPath, { requireRunning: true }).catch((error) => ({
|
|
395
516
|
ok: false,
|
|
396
517
|
error: error instanceof Error ? error.message : String(error),
|
|
397
518
|
}));
|
|
398
|
-
if (probe.ok) {
|
|
519
|
+
if (probe.ok || options.replaceUnhealthy !== true) {
|
|
399
520
|
return {
|
|
400
521
|
...snapshotGateway(existing),
|
|
401
522
|
probe,
|
|
402
523
|
};
|
|
403
524
|
}
|
|
525
|
+
|
|
404
526
|
stopHermesGateway(projectPath);
|
|
405
527
|
}
|
|
406
528
|
|
|
@@ -408,12 +530,15 @@ export async function ensureHermesGateway(options = {}) {
|
|
|
408
530
|
const port = await findAvailablePort(Number(options.port || process.env.HERMES_API_SERVER_PORT || DEFAULT_PORT), host);
|
|
409
531
|
const apiServerKey = options.apiServerKey || makeApiServerKey();
|
|
410
532
|
const appRoot = options.appRoot || process.cwd();
|
|
533
|
+
const sourceHermesHome = options.sourceHermesHome || resolveSourceHermesHome(process.env);
|
|
534
|
+
const hermesHome = resolveHermesGatewayHome(process.env, options);
|
|
411
535
|
const env = buildHermesGatewayEnv(process.env, {
|
|
412
536
|
...options,
|
|
413
537
|
host,
|
|
414
538
|
port,
|
|
415
539
|
apiServerKey,
|
|
416
540
|
appRoot,
|
|
541
|
+
hermesHome,
|
|
417
542
|
});
|
|
418
543
|
const installStatus = readHermesInstallStatus(env, {
|
|
419
544
|
allowSmokeHermes: options.allowSmokeHermes === true,
|
|
@@ -429,6 +554,7 @@ export async function ensureHermesGateway(options = {}) {
|
|
|
429
554
|
host,
|
|
430
555
|
port,
|
|
431
556
|
baseUrl: gatewayBaseUrl(host, port),
|
|
557
|
+
hermesHome,
|
|
432
558
|
apiServerKey,
|
|
433
559
|
command: installStatus.command,
|
|
434
560
|
child: null,
|
|
@@ -442,6 +568,7 @@ export async function ensureHermesGateway(options = {}) {
|
|
|
442
568
|
};
|
|
443
569
|
gateways.set(projectPath, gateway);
|
|
444
570
|
|
|
571
|
+
seedHermesGatewayHome({ sourceHome: sourceHermesHome, targetHome: hermesHome, gateway });
|
|
445
572
|
await configurePixcodeMcp({ appRoot, env, gateway });
|
|
446
573
|
|
|
447
574
|
const gatewayArgs = options.gatewayArgs || ['gateway', 'run', '--replace'];
|
|
@@ -12,6 +12,7 @@ const POSIX_INSTALLER_URL = 'https://raw.githubusercontent.com/NousResearch/herm
|
|
|
12
12
|
const WINDOWS_INSTALLER_URL = 'https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1';
|
|
13
13
|
const FINISHED_TTL_MS = 10 * 60 * 1000;
|
|
14
14
|
const HARD_TIMEOUT_MS = 20 * 60 * 1000;
|
|
15
|
+
const HERMES_VERSION_TIMEOUT_MS = 20 * 1000;
|
|
15
16
|
const jobs = new Map();
|
|
16
17
|
|
|
17
18
|
function pathSeparator() {
|
|
@@ -125,13 +126,22 @@ function pushHermesCommandFiles(candidates, dir) {
|
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
function hermesVersionTimeoutMs(env = process.env) {
|
|
130
|
+
const configured = Number(env.HERMES_VERSION_TIMEOUT_MS);
|
|
131
|
+
if (Number.isFinite(configured) && configured >= 1000) {
|
|
132
|
+
return configured;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return HERMES_VERSION_TIMEOUT_MS;
|
|
136
|
+
}
|
|
137
|
+
|
|
128
138
|
function runHermesVersion(candidate, env) {
|
|
129
139
|
try {
|
|
130
140
|
const result = spawn.sync(candidate, ['--version'], {
|
|
131
141
|
encoding: 'utf8',
|
|
132
142
|
env,
|
|
133
143
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
134
|
-
timeout:
|
|
144
|
+
timeout: hermesVersionTimeoutMs(env),
|
|
135
145
|
windowsHide: true,
|
|
136
146
|
});
|
|
137
147
|
if (result.error || result.status !== 0) {
|