@pixelbyte-software/pixcode 1.51.8 → 1.51.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.
- package/CODE_OF_CONDUCT.md +41 -41
- package/CONTRIBUTING.md +155 -155
- package/LICENSE +718 -718
- package/README.de.md +169 -169
- package/README.ja.md +167 -167
- package/README.ko.md +167 -167
- package/README.md +419 -419
- package/README.ru.md +169 -169
- package/README.tr.md +298 -298
- package/README.zh-CN.md +167 -167
- package/SECURITY.md +46 -46
- package/dist/api-automation.html +110 -110
- package/dist/api-docs.html +548 -548
- package/dist/assets/{index--kNEhygZ.js → index-Gs7C4hZT.js} +69 -69
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/docs.html +308 -308
- package/dist/favicon.svg +8 -8
- package/dist/features.html +133 -133
- package/dist/generate-icons.js +48 -48
- package/dist/humans.txt +15 -15
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +9 -9
- package/dist/icons/icon-144x144.svg +9 -9
- package/dist/icons/icon-152x152.svg +9 -9
- package/dist/icons/icon-192x192.svg +9 -9
- package/dist/icons/icon-384x384.svg +9 -9
- package/dist/icons/icon-512x512.svg +9 -9
- package/dist/icons/icon-72x72.svg +9 -9
- package/dist/icons/icon-96x96.svg +9 -9
- package/dist/icons/icon-template.svg +9 -9
- package/dist/icons/qwen-logo.svg +14 -14
- package/dist/index.html +58 -58
- package/dist/landing.html +268 -268
- package/dist/llms-full.txt +119 -119
- package/dist/llms.txt +53 -53
- package/dist/logo.svg +12 -12
- package/dist/manifest.json +60 -60
- package/dist/openapi.yaml +1696 -1696
- package/dist/orchestration.html +125 -125
- package/dist/robots.txt +4 -4
- package/dist/site.css +692 -692
- package/dist/sitemap.xml +51 -51
- package/dist/sw.js +132 -132
- package/dist-server/server/cli.js +96 -96
- package/dist-server/server/daemon/manager.js +33 -33
- package/dist-server/server/daemon-manager.js +64 -64
- package/dist-server/server/index.js +91 -65
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/routes/commands.js +25 -25
- package/dist-server/server/routes/git.js +17 -17
- package/dist-server/server/routes/live-view.js +46 -46
- package/dist-server/server/services/hermes-gateway.js +5 -5
- package/dist-server/server/services/public-api-manifest.js +51 -51
- package/package.json +222 -222
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/github/create-v1.38-issues.mjs +351 -351
- package/scripts/github/create-vscode-workbench-issues.mjs +121 -121
- package/scripts/hermes/configure-pixcode-mcp.mjs +165 -165
- package/scripts/hermes/pixcode-mcp-server.mjs +1009 -1009
- package/scripts/smoke/changes-panel-layout.mjs +48 -48
- package/scripts/smoke/chat-composer-fixed-layout.mjs +55 -55
- package/scripts/smoke/chat-message-timeline-order.mjs +41 -41
- package/scripts/smoke/chat-realtime-hydration.mjs +44 -44
- package/scripts/smoke/chat-session-provider-pools.mjs +35 -35
- package/scripts/smoke/chat-session-state.mjs +19 -19
- package/scripts/smoke/code-editor-theme.mjs +55 -55
- package/scripts/smoke/code-editor-vscode-engine.mjs +91 -91
- package/scripts/smoke/command-center-agent-writes.mjs +79 -79
- package/scripts/smoke/command-center-non-git.mjs +46 -46
- package/scripts/smoke/context-packet.mjs +43 -43
- package/scripts/smoke/control-room-ux-redesign.mjs +91 -91
- package/scripts/smoke/daemon-entrypoint.mjs +20 -20
- package/scripts/smoke/default-landing-routing.mjs +33 -33
- package/scripts/smoke/desktop-native-notifications.mjs +30 -30
- package/scripts/smoke/desktop-tray-icon.mjs +33 -33
- package/scripts/smoke/discord-release-workflow.mjs +24 -24
- package/scripts/smoke/git-install-update.mjs +255 -255
- package/scripts/smoke/handoff-artifact-protocol.mjs +50 -50
- package/scripts/smoke/hermes-api-install.mjs +56 -56
- package/scripts/smoke/hermes-gateway-persistence.mjs +104 -104
- package/scripts/smoke/hermes-mcp-pixcode-roundtrip.mjs +426 -426
- package/scripts/smoke/hermes-rest-chat-api.mjs +162 -162
- package/scripts/smoke/hermes-rest-chat-live.mjs +45 -45
- package/scripts/smoke/hermes-rest-codex-launch.mjs +209 -209
- package/scripts/smoke/hermes-rest-gateway.mjs +79 -79
- package/scripts/smoke/hermes-rest-live.mjs +42 -42
- package/scripts/smoke/hermes-roundtrip.mjs +167 -167
- package/scripts/smoke/hermes-settings-commands.mjs +349 -349
- package/scripts/smoke/hermes-smoke-launcher-guard.mjs +34 -34
- package/scripts/smoke/live-view-diagnostics.mjs +53 -53
- package/scripts/smoke/live-view-environment.mjs +92 -92
- package/scripts/smoke/live-view-integration.mjs +450 -450
- package/scripts/smoke/mac-desktop-runtime.mjs +37 -37
- package/scripts/smoke/mobile-tunnel-guidance.mjs +29 -29
- package/scripts/smoke/model-registry.mjs +36 -36
- package/scripts/smoke/multi-project-ui.mjs +45 -45
- package/scripts/smoke/multi-worker-slots.mjs +42 -42
- package/scripts/smoke/notification-center.mjs +87 -87
- package/scripts/smoke/notification-inapp-preference.mjs +23 -23
- package/scripts/smoke/notification-taxonomy.mjs +58 -58
- package/scripts/smoke/orchestration-api.mjs +172 -172
- package/scripts/smoke/orchestration-execution-dashboard.mjs +33 -33
- package/scripts/smoke/orchestration-live-run.mjs +176 -176
- package/scripts/smoke/orchestration-mobile-scroll.mjs +29 -29
- package/scripts/smoke/orchestration-model-sync.mjs +30 -30
- package/scripts/smoke/orchestration-permission-fallback.mjs +34 -34
- package/scripts/smoke/orchestration-runtime-guards.mjs +48 -48
- package/scripts/smoke/orchestration-user-facing-output.mjs +25 -25
- package/scripts/smoke/permission-policy.mjs +50 -50
- package/scripts/smoke/pixcode-workbench-1-48.mjs +167 -167
- package/scripts/smoke/provider-models-opencode-live.mjs +66 -66
- package/scripts/smoke/provider-rest-api.mjs +124 -124
- package/scripts/smoke/provider-selection-status.mjs +52 -52
- package/scripts/smoke/run-state-refresh.mjs +52 -52
- package/scripts/smoke/runtime-manager.mjs +99 -99
- package/scripts/smoke/shell-manual-disconnect.mjs +30 -30
- package/scripts/smoke/side-panel-editor-layout.mjs +34 -34
- package/scripts/smoke/static-root-routing.mjs +21 -21
- package/scripts/smoke/strict-handoff-compact.mjs +60 -60
- package/scripts/smoke/taskmaster-config.mjs +24 -24
- package/scripts/smoke/taskmaster-execution-telegram.mjs +3 -3
- package/scripts/smoke/taskmaster-onboarding.mjs +3 -3
- package/scripts/smoke/taskmaster-run-graph.mjs +3 -3
- package/scripts/smoke/telegram-control.mjs +242 -242
- package/scripts/smoke/tunnel-persistence.mjs +56 -56
- package/scripts/smoke/update-issue-progress.mjs +69 -69
- package/scripts/smoke/update-ux.mjs +55 -55
- package/scripts/smoke/v138-completion.mjs +132 -132
- package/scripts/smoke/v138-desktop-release-hardening.mjs +69 -69
- package/scripts/smoke/v138-diagnostics.mjs +63 -63
- package/scripts/smoke/v138-issue-planner.mjs +33 -33
- package/scripts/smoke/v143-remote-control.mjs +76 -76
- package/scripts/smoke/v144-production-loop.mjs +47 -47
- package/scripts/smoke/v145-platformization.mjs +46 -46
- package/scripts/smoke/v146-control-room-ui.mjs +150 -150
- package/scripts/smoke/version-modal-autoshow.mjs +29 -29
- package/scripts/smoke/vscode-workbench-layout.mjs +63 -63
- package/scripts/smoke/vscode-workbench-polish.mjs +461 -461
- package/scripts/smoke/workflow-fallback-replay.mjs +56 -56
- package/scripts/smoke/workflow-templates.mjs +43 -43
- package/scripts/smoke/workflow-trace-timeline.mjs +46 -46
- package/scripts/update-git-install.mjs +283 -283
- package/server/claude-sdk.js +920 -920
- package/server/cli.js +1035 -1035
- package/server/constants/config.js +4 -4
- package/server/cursor-cli.js +344 -344
- package/server/daemon/manager.js +563 -563
- package/server/daemon-manager.js +964 -964
- package/server/database/db.js +895 -895
- package/server/database/json-store.js +197 -197
- package/server/gemini-cli.js +550 -550
- package/server/gemini-response-handler.js +79 -79
- package/server/index.js +111 -81
- package/server/load-env.js +35 -35
- package/server/middleware/auth.js +155 -155
- package/server/modules/orchestration/a2a/adapter-registry.ts +108 -108
- package/server/modules/orchestration/a2a/adapters/abstract-a2a.adapter.ts +63 -63
- package/server/modules/orchestration/a2a/adapters/claude-code.adapter.ts +286 -286
- package/server/modules/orchestration/a2a/adapters/codex.adapter.ts +244 -244
- package/server/modules/orchestration/a2a/adapters/cursor.adapter.ts +249 -249
- package/server/modules/orchestration/a2a/adapters/gemini.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/json-event.adapter.ts +100 -100
- package/server/modules/orchestration/a2a/adapters/opencode.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/adapters/qwen.adapter.ts +248 -248
- package/server/modules/orchestration/a2a/agent-card.ts +55 -55
- package/server/modules/orchestration/a2a/routes.ts +590 -590
- package/server/modules/orchestration/a2a/task-store.ts +178 -178
- package/server/modules/orchestration/a2a/types.ts +126 -126
- package/server/modules/orchestration/a2a/validator.ts +113 -113
- package/server/modules/orchestration/hermes/hermes.routes.ts +642 -642
- package/server/modules/orchestration/index.ts +101 -101
- package/server/modules/orchestration/preview/port-watcher.ts +112 -112
- package/server/modules/orchestration/preview/preview-proxy.ts +60 -60
- package/server/modules/orchestration/preview/types.ts +19 -19
- package/server/modules/orchestration/security/permission-policy.ts +401 -401
- package/server/modules/orchestration/tasks/orchestration-task-store.ts +41 -41
- package/server/modules/orchestration/tasks/orchestration-task.routes.ts +64 -64
- package/server/modules/orchestration/tasks/orchestration-task.service.ts +209 -209
- package/server/modules/orchestration/tasks/orchestration-task.types.ts +40 -40
- package/server/modules/orchestration/tasks/task-run-graph.ts +155 -155
- package/server/modules/orchestration/workflows/approval-queue.ts +106 -106
- package/server/modules/orchestration/workflows/built-in-workflows.ts +127 -127
- package/server/modules/orchestration/workflows/context-packet.ts +186 -186
- package/server/modules/orchestration/workflows/handoff-artifact.ts +175 -175
- package/server/modules/orchestration/workflows/workflow-fallback-policy.ts +161 -161
- package/server/modules/orchestration/workflows/workflow-replay.ts +254 -254
- package/server/modules/orchestration/workflows/workflow-runner.ts +2070 -2070
- package/server/modules/orchestration/workflows/workflow-store.ts +97 -97
- package/server/modules/orchestration/workflows/workflow-templates.ts +272 -272
- package/server/modules/orchestration/workflows/workflow-trace.ts +424 -424
- package/server/modules/orchestration/workflows/workflow.routes.ts +586 -586
- package/server/modules/orchestration/workflows/workflow.types.ts +111 -111
- package/server/modules/orchestration/workflows/workspace-target.ts +122 -122
- package/server/modules/orchestration/workspace/docker-workspace.ts +136 -136
- package/server/modules/orchestration/workspace/path-safety.ts +55 -55
- package/server/modules/orchestration/workspace/types.ts +52 -52
- package/server/modules/orchestration/workspace/workspace-manager.ts +102 -102
- package/server/modules/orchestration/workspace/worktree-workspace.ts +126 -126
- package/server/modules/providers/index.ts +2 -2
- package/server/modules/providers/list/claude/claude-auth.provider.ts +146 -146
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +306 -306
- package/server/modules/providers/list/claude/claude.provider.ts +15 -15
- package/server/modules/providers/list/codex/codex-auth.provider.ts +117 -117
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +319 -319
- package/server/modules/providers/list/codex/codex.provider.ts +15 -15
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +147 -147
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -421
- package/server/modules/providers/list/cursor/cursor.provider.ts +15 -15
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +173 -173
- package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -110
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +227 -227
- package/server/modules/providers/list/gemini/gemini.provider.ts +15 -15
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +131 -131
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +126 -126
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +286 -286
- package/server/modules/providers/list/opencode/opencode.provider.ts +29 -29
- package/server/modules/providers/list/qwen/qwen-auth.provider.ts +146 -146
- package/server/modules/providers/list/qwen/qwen-mcp.provider.ts +114 -114
- package/server/modules/providers/list/qwen/qwen-sessions.provider.ts +265 -265
- package/server/modules/providers/list/qwen/qwen.provider.ts +21 -21
- package/server/modules/providers/provider.registry.ts +40 -40
- package/server/modules/providers/provider.routes.ts +891 -891
- package/server/modules/providers/services/mcp.service.ts +86 -86
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/sessions.service.ts +45 -45
- package/server/modules/providers/shared/base/abstract.provider.ts +20 -20
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/provider-configs.ts +142 -142
- package/server/modules/providers/tests/mcp.test.ts +293 -293
- package/server/openai-codex.js +462 -462
- package/server/opencode-cli.js +491 -491
- package/server/opencode-response-handler.js +111 -111
- package/server/projects.js +3008 -3008
- package/server/qwen-code-cli.js +410 -410
- package/server/qwen-response-handler.js +73 -73
- package/server/routes/agent.js +1435 -1435
- package/server/routes/auth.js +142 -142
- package/server/routes/codex.js +20 -20
- package/server/routes/commands.js +538 -538
- package/server/routes/cursor.js +61 -61
- package/server/routes/diagnostics.js +41 -41
- package/server/routes/gemini.js +25 -25
- package/server/routes/git.js +1641 -1641
- package/server/routes/live-view.js +387 -387
- package/server/routes/mcp-utils.js +13 -13
- package/server/routes/messages.js +50 -50
- package/server/routes/network.js +117 -117
- package/server/routes/platformization.js +182 -182
- package/server/routes/plugins.js +320 -320
- package/server/routes/production-agent-loop.js +90 -90
- package/server/routes/projects.js +911 -911
- package/server/routes/public-api.js +34 -34
- package/server/routes/qwen.js +27 -27
- package/server/routes/remote.js +50 -50
- package/server/routes/settings.js +321 -321
- package/server/routes/telegram.js +140 -140
- package/server/routes/user.js +125 -125
- package/server/routes/webhooks.js +63 -63
- package/server/services/control-room.js +102 -102
- package/server/services/diagnostics.js +165 -165
- package/server/services/external-access.js +375 -375
- package/server/services/hermes-gateway.js +1562 -1562
- package/server/services/hermes-install-jobs.js +729 -729
- package/server/services/install-jobs.js +715 -715
- package/server/services/live-view.js +956 -956
- package/server/services/managed-runtimes.js +493 -493
- package/server/services/model-registry.js +144 -144
- package/server/services/notification-orchestrator.js +365 -365
- package/server/services/notification-taxonomy.js +204 -204
- package/server/services/platformization.js +773 -773
- package/server/services/production-agent-loop.js +248 -248
- package/server/services/provider-cli-versions.js +149 -149
- package/server/services/provider-credentials.js +189 -189
- package/server/services/provider-models.js +396 -396
- package/server/services/public-api-manifest.js +190 -190
- package/server/services/remote-connection.js +127 -127
- package/server/services/runtime-manager.js +323 -323
- package/server/services/startup-update.js +234 -234
- package/server/services/telegram/bot.js +331 -331
- package/server/services/telegram/control-center.js +979 -979
- package/server/services/telegram/telegram-http-client.js +151 -151
- package/server/services/telegram/translations.js +340 -340
- package/server/services/vapid-keys.js +36 -36
- package/server/services/webhooks.js +216 -216
- package/server/sessionManager.js +225 -225
- package/server/shared/interfaces.ts +54 -54
- package/server/shared/types.ts +172 -172
- package/server/shared/utils.ts +193 -193
- package/server/tsconfig.json +36 -36
- package/server/utils/colors.js +21 -21
- package/server/utils/commandParser.js +305 -305
- package/server/utils/frontmatter.js +18 -18
- package/server/utils/gitConfig.js +34 -34
- package/server/utils/plugin-loader.js +457 -457
- package/server/utils/plugin-process-manager.js +185 -185
- package/server/utils/port-access.js +209 -209
- package/server/utils/runtime-paths.js +37 -37
- package/server/utils/url-detection.js +71 -71
- package/server/vite-daemon.js +79 -79
- package/shared/modelConstants.js +161 -161
- package/shared/networkHosts.js +22 -22
|
@@ -1,426 +1,426 @@
|
|
|
1
|
-
import assert from 'node:assert/strict';
|
|
2
|
-
import { createServer } from 'node:http';
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
8
|
-
const apiKey = 'px_smoke_key';
|
|
9
|
-
const seen = [];
|
|
10
|
-
const providerMcpUpserts = [];
|
|
11
|
-
const terminalLaunches = [];
|
|
12
|
-
const providerOutputReads = [];
|
|
13
|
-
const providerInputWrites = [];
|
|
14
|
-
const gatewayRequests = [];
|
|
15
|
-
|
|
16
|
-
function readJson(req) {
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
let body = '';
|
|
19
|
-
req.setEncoding('utf8');
|
|
20
|
-
req.on('data', (chunk) => { body += chunk; });
|
|
21
|
-
req.on('end', () => {
|
|
22
|
-
try {
|
|
23
|
-
resolve(body ? JSON.parse(body) : {});
|
|
24
|
-
} catch (error) {
|
|
25
|
-
reject(error);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const server = createServer(async (req, res) => {
|
|
32
|
-
const url = new URL(req.url || '/', 'http://127.0.0.1');
|
|
33
|
-
seen.push({
|
|
34
|
-
method: req.method,
|
|
35
|
-
path: url.pathname,
|
|
36
|
-
auth: req.headers.authorization,
|
|
37
|
-
});
|
|
38
|
-
res.setHeader('content-type', 'application/json');
|
|
39
|
-
|
|
40
|
-
if (req.headers.authorization !== `Bearer ${apiKey}`) {
|
|
41
|
-
res.statusCode = 401;
|
|
42
|
-
res.end(JSON.stringify({ error: 'bad auth' }));
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (req.method === 'GET' && url.pathname === '/api/projects') {
|
|
47
|
-
res.end(JSON.stringify([
|
|
48
|
-
{ name: 'pixcode', displayName: 'Pixcode', fullPath: '/root/pixcode', fileCount: 224 },
|
|
49
|
-
]));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (req.method === 'GET' && url.pathname === '/api/public/manifest') {
|
|
54
|
-
res.end(JSON.stringify({
|
|
55
|
-
name: 'Pixcode Public API',
|
|
56
|
-
groups: [
|
|
57
|
-
{ id: 'projects', basePath: '/api/projects', scopes: ['projects:read', 'projects:write'] },
|
|
58
|
-
{ id: 'providers', basePath: '/api/providers', scopes: ['providers:read', 'providers:write'] },
|
|
59
|
-
],
|
|
60
|
-
}));
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (req.method === 'GET' && url.pathname === '/api/providers/codex/auth/status') {
|
|
65
|
-
res.end(JSON.stringify({ data: { provider: 'codex', installed: true, authenticated: true } }));
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (req.method === 'GET' && url.pathname === '/api/providers/qwen/auth/status') {
|
|
70
|
-
res.end(JSON.stringify({ data: { provider: 'qwen', installed: false, authenticated: false, error: 'Qwen Code CLI is not installed' } }));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (req.method === 'POST' && url.pathname === '/api/providers/codex/mcp/servers') {
|
|
75
|
-
const body = await readJson(req);
|
|
76
|
-
providerMcpUpserts.push(body);
|
|
77
|
-
res.statusCode = 201;
|
|
78
|
-
res.end(JSON.stringify({ data: { server: { provider: 'codex', name: body.name, scope: body.scope, transport: body.transport } } }));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (req.method === 'GET' && url.pathname === '/api/orchestration/hermes/gateway/status') {
|
|
83
|
-
res.end(JSON.stringify({ running: true, baseUrl: 'http://127.0.0.1:8642', projectPath: '/root/pixcode' }));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (req.method === 'POST' && url.pathname === '/api/orchestration/hermes/gateway/probe') {
|
|
88
|
-
const body = await readJson(req);
|
|
89
|
-
res.end(JSON.stringify({
|
|
90
|
-
ok: true,
|
|
91
|
-
projectPath: body.projectPath || '/root/pixcode',
|
|
92
|
-
checks: {
|
|
93
|
-
health: { ok: true, status: 200 },
|
|
94
|
-
capabilities: { ok: true, status: 200 },
|
|
95
|
-
models: { ok: true, status: 200 },
|
|
96
|
-
},
|
|
97
|
-
}));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (req.method === 'GET' && url.pathname === '/api/orchestration/hermes/diagnostics') {
|
|
102
|
-
res.end(JSON.stringify({
|
|
103
|
-
ok: true,
|
|
104
|
-
model: { provider: 'openai-codex', default: 'gpt-5.5' },
|
|
105
|
-
config: {
|
|
106
|
-
active: {
|
|
107
|
-
toolsets: ['hermes-cli', 'mcp-pixcode'],
|
|
108
|
-
pixcodeMcp: { toolCount: 12, missingTools: [] },
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
cron: { toolsetAvailable: true, gatewayJobsApi: { ok: true, status: 200 } },
|
|
112
|
-
issues: [],
|
|
113
|
-
}));
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (req.method === 'GET' && url.pathname === '/api/orchestration/hermes/control-plane') {
|
|
118
|
-
res.end(JSON.stringify({
|
|
119
|
-
ok: true,
|
|
120
|
-
homes: {
|
|
121
|
-
source: '/root/.hermes',
|
|
122
|
-
managed: '/root/.hermes/profiles/pixcode',
|
|
123
|
-
},
|
|
124
|
-
activeProfile: 'default',
|
|
125
|
-
profiles: [
|
|
126
|
-
{
|
|
127
|
-
name: 'default',
|
|
128
|
-
isActive: true,
|
|
129
|
-
sessions: { total: 3, exists: true },
|
|
130
|
-
cron: { total: 1, active: 1, exists: true },
|
|
131
|
-
tools: { pixcodeMcpReady: true, pixcodeMcpToolCount: 14, missingPixcodeMcpTools: [] },
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
capabilities: [
|
|
135
|
-
{ id: 'rest-gateway', label: 'Hermes REST gateway', ready: true },
|
|
136
|
-
{ id: 'pixcode-mcp', label: 'Pixcode MCP tools', ready: true },
|
|
137
|
-
],
|
|
138
|
-
recommendations: [],
|
|
139
|
-
}));
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (req.method === 'POST' && url.pathname === '/api/orchestration/hermes/control-plane/repair') {
|
|
144
|
-
const body = await readJson(req);
|
|
145
|
-
res.end(JSON.stringify({
|
|
146
|
-
ok: true,
|
|
147
|
-
gateway: { running: true, projectPath: body.projectPath || '/root/pixcode' },
|
|
148
|
-
controlPlane: {
|
|
149
|
-
ok: true,
|
|
150
|
-
profiles: [
|
|
151
|
-
{
|
|
152
|
-
name: 'pixcode',
|
|
153
|
-
tools: { pixcodeMcpReady: true, pixcodeMcpToolCount: 14, missingPixcodeMcpTools: [] },
|
|
154
|
-
},
|
|
155
|
-
],
|
|
156
|
-
},
|
|
157
|
-
}));
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (req.method === 'POST' && url.pathname === '/api/orchestration/hermes/gateway/request') {
|
|
162
|
-
const body = await readJson(req);
|
|
163
|
-
gatewayRequests.push(body);
|
|
164
|
-
res.end(JSON.stringify({
|
|
165
|
-
ok: true,
|
|
166
|
-
endpoint: body.endpoint,
|
|
167
|
-
body: {
|
|
168
|
-
jobs: [
|
|
169
|
-
{ job_id: 'job_1', name: 'Morning repo check', schedule: '0 9 * * *' },
|
|
170
|
-
],
|
|
171
|
-
},
|
|
172
|
-
}));
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (req.method === 'POST' && url.pathname === '/api/orchestration/hermes/terminal-launches') {
|
|
177
|
-
const body = await readJson(req);
|
|
178
|
-
terminalLaunches.push(body);
|
|
179
|
-
res.statusCode = 201;
|
|
180
|
-
res.end(JSON.stringify({
|
|
181
|
-
event: {
|
|
182
|
-
id: terminalLaunches.length,
|
|
183
|
-
provider: body.provider,
|
|
184
|
-
projectPath: body.projectPath,
|
|
185
|
-
prompt: body.prompt,
|
|
186
|
-
source: 'hermes-mcp',
|
|
187
|
-
},
|
|
188
|
-
}));
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (req.method === 'GET' && url.pathname === '/api/shell/sessions/provider-output') {
|
|
193
|
-
providerOutputReads.push({
|
|
194
|
-
provider: url.searchParams.get('provider'),
|
|
195
|
-
projectPath: url.searchParams.get('projectPath'),
|
|
196
|
-
launchId: url.searchParams.get('launchId'),
|
|
197
|
-
});
|
|
198
|
-
const outputs = [
|
|
199
|
-
{
|
|
200
|
-
active: true,
|
|
201
|
-
provider: 'codex',
|
|
202
|
-
projectPath: '/root/pixcode',
|
|
203
|
-
terminalState: 'busy',
|
|
204
|
-
output: 'OpenAI Codex\n› /init\n\n• Working (10s • esc to interrupt)\n',
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
active: true,
|
|
208
|
-
provider: 'codex',
|
|
209
|
-
projectPath: '/root/pixcode',
|
|
210
|
-
terminalState: 'busy',
|
|
211
|
-
output: 'OpenAI Codex\n› /init\n\n• Ran npm test\n• Working (30s • esc to interrupt)\n',
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
active: true,
|
|
215
|
-
provider: 'codex',
|
|
216
|
-
projectPath: '/root/pixcode',
|
|
217
|
-
terminalState: 'idle',
|
|
218
|
-
output: 'OpenAI Codex\n› /init\n\n• Ran npm test\n\n› Implement {feature}\n',
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
active: true,
|
|
222
|
-
provider: 'codex',
|
|
223
|
-
projectPath: '/root/pixcode',
|
|
224
|
-
terminalState: 'idle',
|
|
225
|
-
output: 'Baseline check passed: npm test reports 195 passing, 0 failing.\n\n› Use /skills to list available skills\n',
|
|
226
|
-
},
|
|
227
|
-
];
|
|
228
|
-
res.end(JSON.stringify(outputs[Math.min(providerOutputReads.length - 1, outputs.length - 1)]));
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (req.method === 'POST' && url.pathname === '/api/shell/sessions/provider-input') {
|
|
233
|
-
const body = await readJson(req);
|
|
234
|
-
providerInputWrites.push(body);
|
|
235
|
-
res.end(JSON.stringify({ ok: true, wrote: true, provider: body.provider, launchId: body.launchId }));
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
res.statusCode = 404;
|
|
240
|
-
res.end(JSON.stringify({ error: url.pathname }));
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
function listen() {
|
|
244
|
-
return new Promise((resolve) => {
|
|
245
|
-
server.listen(0, '127.0.0.1', () => {
|
|
246
|
-
const address = server.address();
|
|
247
|
-
resolve(`http://127.0.0.1:${address.port}`);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function callMcp(child, method, params = undefined) {
|
|
253
|
-
const id = callMcp.nextId;
|
|
254
|
-
callMcp.nextId += 1;
|
|
255
|
-
const payload = { jsonrpc: '2.0', id, method, ...(params ? { params } : {}) };
|
|
256
|
-
child.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
257
|
-
return new Promise((resolve, reject) => {
|
|
258
|
-
const timeout = setTimeout(() => {
|
|
259
|
-
reject(new Error(`MCP call timed out: ${method}`));
|
|
260
|
-
}, 10000);
|
|
261
|
-
const onLine = (line) => {
|
|
262
|
-
if (!line.trim()) return;
|
|
263
|
-
const message = JSON.parse(line);
|
|
264
|
-
if (message.id !== id) return;
|
|
265
|
-
clearTimeout(timeout);
|
|
266
|
-
child.stdout.off('data', onData);
|
|
267
|
-
if (message.error) {
|
|
268
|
-
reject(new Error(message.error.message));
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
resolve(message.result);
|
|
272
|
-
};
|
|
273
|
-
const onData = (chunk) => {
|
|
274
|
-
callMcp.buffer += chunk.toString();
|
|
275
|
-
let index;
|
|
276
|
-
while ((index = callMcp.buffer.indexOf('\n')) !== -1) {
|
|
277
|
-
const line = callMcp.buffer.slice(0, index);
|
|
278
|
-
callMcp.buffer = callMcp.buffer.slice(index + 1);
|
|
279
|
-
onLine(line);
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
child.stdout.on('data', onData);
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
callMcp.nextId = 1;
|
|
286
|
-
callMcp.buffer = '';
|
|
287
|
-
|
|
288
|
-
const baseUrl = await listen();
|
|
289
|
-
const child = spawn(process.execPath, [path.join(repoRoot, 'scripts/hermes/pixcode-mcp-server.mjs')], {
|
|
290
|
-
cwd: repoRoot,
|
|
291
|
-
env: {
|
|
292
|
-
...process.env,
|
|
293
|
-
PIXCODE_BASE_URL: baseUrl,
|
|
294
|
-
PIXCODE_API_KEY: apiKey,
|
|
295
|
-
PIXCODE_MCP_READBACK_IDLE_STABLE_MS: '500',
|
|
296
|
-
},
|
|
297
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
try {
|
|
301
|
-
await callMcp(child, 'initialize', { protocolVersion: '2024-11-05' });
|
|
302
|
-
const tools = await callMcp(child, 'tools/list');
|
|
303
|
-
const toolNames = tools.tools.map((tool) => tool.name);
|
|
304
|
-
assert(toolNames.includes('pixcode_list_projects'), 'list projects tool missing');
|
|
305
|
-
assert(toolNames.includes('pixcode_get_provider_status'), 'provider status tool missing');
|
|
306
|
-
assert(toolNames.includes('pixcode_get_hermes_gateway_status'), 'Hermes gateway status tool missing');
|
|
307
|
-
assert(toolNames.includes('pixcode_probe_hermes_gateway'), 'Hermes gateway probe tool missing');
|
|
308
|
-
assert(toolNames.includes('pixcode_get_hermes_diagnostics'), 'Hermes diagnostics tool missing');
|
|
309
|
-
assert(toolNames.includes('pixcode_get_hermes_control_plane'), 'Hermes control-plane snapshot tool missing');
|
|
310
|
-
assert(toolNames.includes('pixcode_repair_hermes_control_plane'), 'Hermes control-plane repair tool missing');
|
|
311
|
-
assert(toolNames.includes('pixcode_get_api_manifest'), 'Pixcode API manifest tool missing');
|
|
312
|
-
assert(toolNames.includes('pixcode_api_request'), 'Pixcode generic API request tool missing');
|
|
313
|
-
assert(toolNames.includes('pixcode_hermes_gateway_request'), 'Hermes gateway request proxy tool missing');
|
|
314
|
-
assert(toolNames.includes('pixcode_manage_hermes_cron'), 'Hermes cron management tool missing');
|
|
315
|
-
assert(toolNames.includes('pixcode_send_cli_input'), 'Provider terminal input tool missing');
|
|
316
|
-
|
|
317
|
-
const projects = await callMcp(child, 'tools/call', { name: 'pixcode_list_projects', arguments: {} });
|
|
318
|
-
assert.match(projects.content[0].text, /224/, 'projects response should include file count');
|
|
319
|
-
|
|
320
|
-
const status = await callMcp(child, 'tools/call', { name: 'pixcode_get_hermes_gateway_status', arguments: {} });
|
|
321
|
-
assert.match(status.content[0].text, /127\.0\.0\.1:8642/, 'gateway status should return REST base URL');
|
|
322
|
-
|
|
323
|
-
const probe = await callMcp(child, 'tools/call', {
|
|
324
|
-
name: 'pixcode_probe_hermes_gateway',
|
|
325
|
-
arguments: { projectPath: '/root/pixcode' },
|
|
326
|
-
});
|
|
327
|
-
assert.match(probe.content[0].text, /"ok": true/, 'gateway probe should return ok');
|
|
328
|
-
|
|
329
|
-
const diagnostics = await callMcp(child, 'tools/call', {
|
|
330
|
-
name: 'pixcode_get_hermes_diagnostics',
|
|
331
|
-
arguments: { projectPath: '/root/pixcode' },
|
|
332
|
-
});
|
|
333
|
-
assert.match(diagnostics.content[0].text, /mcp-pixcode/, 'Hermes diagnostics should expose active toolsets.');
|
|
334
|
-
assert.match(diagnostics.content[0].text, /"toolsetAvailable": true/, 'Hermes diagnostics should expose cron toolset availability.');
|
|
335
|
-
|
|
336
|
-
const controlPlane = await callMcp(child, 'tools/call', {
|
|
337
|
-
name: 'pixcode_get_hermes_control_plane',
|
|
338
|
-
arguments: { projectPath: '/root/pixcode' },
|
|
339
|
-
});
|
|
340
|
-
assert.match(controlPlane.content[0].text, /Hermes REST gateway/, 'Hermes control-plane tool should expose capability state.');
|
|
341
|
-
assert.match(controlPlane.content[0].text, /"pixcodeMcpReady": true/, 'Hermes control-plane tool should expose MCP readiness.');
|
|
342
|
-
|
|
343
|
-
const repairedControlPlane = await callMcp(child, 'tools/call', {
|
|
344
|
-
name: 'pixcode_repair_hermes_control_plane',
|
|
345
|
-
arguments: { projectPath: '/root/pixcode', forceRestart: true },
|
|
346
|
-
});
|
|
347
|
-
assert.match(repairedControlPlane.content[0].text, /"ok": true/, 'Hermes control-plane repair tool should return repaired state.');
|
|
348
|
-
|
|
349
|
-
const manifest = await callMcp(child, 'tools/call', { name: 'pixcode_get_api_manifest', arguments: {} });
|
|
350
|
-
assert.match(manifest.content[0].text, /Pixcode Public API/, 'API manifest tool should expose Pixcode API docs to Hermes.');
|
|
351
|
-
|
|
352
|
-
const apiRequest = await callMcp(child, 'tools/call', {
|
|
353
|
-
name: 'pixcode_api_request',
|
|
354
|
-
arguments: { method: 'GET', path: '/api/projects' },
|
|
355
|
-
});
|
|
356
|
-
assert.match(apiRequest.content[0].text, /"name": "pixcode"/, 'generic Pixcode API tool should call allowlisted local API paths.');
|
|
357
|
-
|
|
358
|
-
const cronJobs = await callMcp(child, 'tools/call', {
|
|
359
|
-
name: 'pixcode_hermes_gateway_request',
|
|
360
|
-
arguments: { method: 'GET', endpoint: '/api/jobs', projectPath: '/root/pixcode' },
|
|
361
|
-
});
|
|
362
|
-
assert.match(cronJobs.content[0].text, /Morning repo check/, 'Hermes gateway request tool should expose API-server jobs/cron endpoints.');
|
|
363
|
-
assert.equal(gatewayRequests[0].endpoint, '/api/jobs', 'Gateway request should keep the requested Hermes endpoint.');
|
|
364
|
-
|
|
365
|
-
const cronList = await callMcp(child, 'tools/call', {
|
|
366
|
-
name: 'pixcode_manage_hermes_cron',
|
|
367
|
-
arguments: { action: 'list', projectPath: '/root/pixcode' },
|
|
368
|
-
});
|
|
369
|
-
assert.match(cronList.content[0].text, /Morning repo check/, 'Hermes cron helper should list managed Hermes jobs.');
|
|
370
|
-
|
|
371
|
-
const launch = await callMcp(child, 'tools/call', {
|
|
372
|
-
name: 'pixcode_open_cli_terminal',
|
|
373
|
-
arguments: { provider: 'codex', projectPath: '/root/pixcode', prompt: 'smoke' },
|
|
374
|
-
});
|
|
375
|
-
assert.match(launch.content[0].text, /hermes-mcp/, 'terminal launch should roundtrip through Pixcode API');
|
|
376
|
-
assert.match(launch.content[0].text, /"pixcodeMcpConfigured": true/, 'terminal launch should configure Pixcode MCP for the selected provider first');
|
|
377
|
-
assert.equal(providerMcpUpserts.length, 1, 'Codex launch should upsert a project-scoped Pixcode MCP server');
|
|
378
|
-
assert.equal(providerMcpUpserts[0].name, 'pixcode', 'Provider MCP server should be named pixcode');
|
|
379
|
-
|
|
380
|
-
providerOutputReads.length = 0;
|
|
381
|
-
const launchWithReadback = await callMcp(child, 'tools/call', {
|
|
382
|
-
name: 'pixcode_open_cli_terminal',
|
|
383
|
-
arguments: {
|
|
384
|
-
provider: 'codex',
|
|
385
|
-
projectPath: '/root/pixcode',
|
|
386
|
-
prompt: 'read final output',
|
|
387
|
-
startupInput: '/init',
|
|
388
|
-
waitForOutputMs: 7000,
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
assert(
|
|
392
|
-
providerOutputReads.length >= 5,
|
|
393
|
-
`readback should keep polling until the provider terminal is idle, reads=${providerOutputReads.length}`,
|
|
394
|
-
);
|
|
395
|
-
assert.match(
|
|
396
|
-
launchWithReadback.content[0].text,
|
|
397
|
-
/195 passing, 0 failing/,
|
|
398
|
-
'readback should return the final Codex output instead of the first working frame',
|
|
399
|
-
);
|
|
400
|
-
assert(
|
|
401
|
-
providerOutputReads.every((read) => read.launchId === '2'),
|
|
402
|
-
`readback should be tied to the Hermes terminal launch id, reads=${JSON.stringify(providerOutputReads)}`,
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
const blockedLaunch = await callMcp(child, 'tools/call', {
|
|
406
|
-
name: 'pixcode_open_cli_terminal',
|
|
407
|
-
arguments: { provider: 'qwen', projectPath: '/root/pixcode', prompt: 'smoke' },
|
|
408
|
-
});
|
|
409
|
-
assert.match(blockedLaunch.content[0].text, /"launched": false/, 'uninstalled providers should not create terminal launches');
|
|
410
|
-
assert.match(blockedLaunch.content[0].text, /"reason": "not_installed"/, 'uninstalled provider response should explain the block');
|
|
411
|
-
assert.equal(terminalLaunches.length, 2, 'Only installed Codex provider launches should be created');
|
|
412
|
-
|
|
413
|
-
const inputWrite = await callMcp(child, 'tools/call', {
|
|
414
|
-
name: 'pixcode_send_cli_input',
|
|
415
|
-
arguments: { provider: 'codex', projectPath: '/root/pixcode', input: 'selam', submit: true, launchId: 2 },
|
|
416
|
-
});
|
|
417
|
-
assert.match(inputWrite.content[0].text, /"wrote": true/, 'MCP should be able to submit input to an existing visible provider terminal.');
|
|
418
|
-
assert.equal(providerInputWrites[0].input, 'selam', 'Provider input body should preserve exact user input.');
|
|
419
|
-
assert.equal(providerInputWrites[0].submit, true, 'Provider input should submit by default when requested.');
|
|
420
|
-
|
|
421
|
-
assert(seen.every((entry) => entry.auth === `Bearer ${apiKey}`), 'all MCP calls should use the Pixcode bearer key');
|
|
422
|
-
console.log('hermes MCP Pixcode roundtrip smoke passed');
|
|
423
|
-
} finally {
|
|
424
|
-
child.kill();
|
|
425
|
-
await new Promise((resolve) => server.close(resolve));
|
|
426
|
-
}
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
8
|
+
const apiKey = 'px_smoke_key';
|
|
9
|
+
const seen = [];
|
|
10
|
+
const providerMcpUpserts = [];
|
|
11
|
+
const terminalLaunches = [];
|
|
12
|
+
const providerOutputReads = [];
|
|
13
|
+
const providerInputWrites = [];
|
|
14
|
+
const gatewayRequests = [];
|
|
15
|
+
|
|
16
|
+
function readJson(req) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
let body = '';
|
|
19
|
+
req.setEncoding('utf8');
|
|
20
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
21
|
+
req.on('end', () => {
|
|
22
|
+
try {
|
|
23
|
+
resolve(body ? JSON.parse(body) : {});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
reject(error);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const server = createServer(async (req, res) => {
|
|
32
|
+
const url = new URL(req.url || '/', 'http://127.0.0.1');
|
|
33
|
+
seen.push({
|
|
34
|
+
method: req.method,
|
|
35
|
+
path: url.pathname,
|
|
36
|
+
auth: req.headers.authorization,
|
|
37
|
+
});
|
|
38
|
+
res.setHeader('content-type', 'application/json');
|
|
39
|
+
|
|
40
|
+
if (req.headers.authorization !== `Bearer ${apiKey}`) {
|
|
41
|
+
res.statusCode = 401;
|
|
42
|
+
res.end(JSON.stringify({ error: 'bad auth' }));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (req.method === 'GET' && url.pathname === '/api/projects') {
|
|
47
|
+
res.end(JSON.stringify([
|
|
48
|
+
{ name: 'pixcode', displayName: 'Pixcode', fullPath: '/root/pixcode', fileCount: 224 },
|
|
49
|
+
]));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (req.method === 'GET' && url.pathname === '/api/public/manifest') {
|
|
54
|
+
res.end(JSON.stringify({
|
|
55
|
+
name: 'Pixcode Public API',
|
|
56
|
+
groups: [
|
|
57
|
+
{ id: 'projects', basePath: '/api/projects', scopes: ['projects:read', 'projects:write'] },
|
|
58
|
+
{ id: 'providers', basePath: '/api/providers', scopes: ['providers:read', 'providers:write'] },
|
|
59
|
+
],
|
|
60
|
+
}));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (req.method === 'GET' && url.pathname === '/api/providers/codex/auth/status') {
|
|
65
|
+
res.end(JSON.stringify({ data: { provider: 'codex', installed: true, authenticated: true } }));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (req.method === 'GET' && url.pathname === '/api/providers/qwen/auth/status') {
|
|
70
|
+
res.end(JSON.stringify({ data: { provider: 'qwen', installed: false, authenticated: false, error: 'Qwen Code CLI is not installed' } }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (req.method === 'POST' && url.pathname === '/api/providers/codex/mcp/servers') {
|
|
75
|
+
const body = await readJson(req);
|
|
76
|
+
providerMcpUpserts.push(body);
|
|
77
|
+
res.statusCode = 201;
|
|
78
|
+
res.end(JSON.stringify({ data: { server: { provider: 'codex', name: body.name, scope: body.scope, transport: body.transport } } }));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (req.method === 'GET' && url.pathname === '/api/orchestration/hermes/gateway/status') {
|
|
83
|
+
res.end(JSON.stringify({ running: true, baseUrl: 'http://127.0.0.1:8642', projectPath: '/root/pixcode' }));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (req.method === 'POST' && url.pathname === '/api/orchestration/hermes/gateway/probe') {
|
|
88
|
+
const body = await readJson(req);
|
|
89
|
+
res.end(JSON.stringify({
|
|
90
|
+
ok: true,
|
|
91
|
+
projectPath: body.projectPath || '/root/pixcode',
|
|
92
|
+
checks: {
|
|
93
|
+
health: { ok: true, status: 200 },
|
|
94
|
+
capabilities: { ok: true, status: 200 },
|
|
95
|
+
models: { ok: true, status: 200 },
|
|
96
|
+
},
|
|
97
|
+
}));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (req.method === 'GET' && url.pathname === '/api/orchestration/hermes/diagnostics') {
|
|
102
|
+
res.end(JSON.stringify({
|
|
103
|
+
ok: true,
|
|
104
|
+
model: { provider: 'openai-codex', default: 'gpt-5.5' },
|
|
105
|
+
config: {
|
|
106
|
+
active: {
|
|
107
|
+
toolsets: ['hermes-cli', 'mcp-pixcode'],
|
|
108
|
+
pixcodeMcp: { toolCount: 12, missingTools: [] },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
cron: { toolsetAvailable: true, gatewayJobsApi: { ok: true, status: 200 } },
|
|
112
|
+
issues: [],
|
|
113
|
+
}));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (req.method === 'GET' && url.pathname === '/api/orchestration/hermes/control-plane') {
|
|
118
|
+
res.end(JSON.stringify({
|
|
119
|
+
ok: true,
|
|
120
|
+
homes: {
|
|
121
|
+
source: '/root/.hermes',
|
|
122
|
+
managed: '/root/.hermes/profiles/pixcode',
|
|
123
|
+
},
|
|
124
|
+
activeProfile: 'default',
|
|
125
|
+
profiles: [
|
|
126
|
+
{
|
|
127
|
+
name: 'default',
|
|
128
|
+
isActive: true,
|
|
129
|
+
sessions: { total: 3, exists: true },
|
|
130
|
+
cron: { total: 1, active: 1, exists: true },
|
|
131
|
+
tools: { pixcodeMcpReady: true, pixcodeMcpToolCount: 14, missingPixcodeMcpTools: [] },
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
capabilities: [
|
|
135
|
+
{ id: 'rest-gateway', label: 'Hermes REST gateway', ready: true },
|
|
136
|
+
{ id: 'pixcode-mcp', label: 'Pixcode MCP tools', ready: true },
|
|
137
|
+
],
|
|
138
|
+
recommendations: [],
|
|
139
|
+
}));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (req.method === 'POST' && url.pathname === '/api/orchestration/hermes/control-plane/repair') {
|
|
144
|
+
const body = await readJson(req);
|
|
145
|
+
res.end(JSON.stringify({
|
|
146
|
+
ok: true,
|
|
147
|
+
gateway: { running: true, projectPath: body.projectPath || '/root/pixcode' },
|
|
148
|
+
controlPlane: {
|
|
149
|
+
ok: true,
|
|
150
|
+
profiles: [
|
|
151
|
+
{
|
|
152
|
+
name: 'pixcode',
|
|
153
|
+
tools: { pixcodeMcpReady: true, pixcodeMcpToolCount: 14, missingPixcodeMcpTools: [] },
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
}));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (req.method === 'POST' && url.pathname === '/api/orchestration/hermes/gateway/request') {
|
|
162
|
+
const body = await readJson(req);
|
|
163
|
+
gatewayRequests.push(body);
|
|
164
|
+
res.end(JSON.stringify({
|
|
165
|
+
ok: true,
|
|
166
|
+
endpoint: body.endpoint,
|
|
167
|
+
body: {
|
|
168
|
+
jobs: [
|
|
169
|
+
{ job_id: 'job_1', name: 'Morning repo check', schedule: '0 9 * * *' },
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
}));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (req.method === 'POST' && url.pathname === '/api/orchestration/hermes/terminal-launches') {
|
|
177
|
+
const body = await readJson(req);
|
|
178
|
+
terminalLaunches.push(body);
|
|
179
|
+
res.statusCode = 201;
|
|
180
|
+
res.end(JSON.stringify({
|
|
181
|
+
event: {
|
|
182
|
+
id: terminalLaunches.length,
|
|
183
|
+
provider: body.provider,
|
|
184
|
+
projectPath: body.projectPath,
|
|
185
|
+
prompt: body.prompt,
|
|
186
|
+
source: 'hermes-mcp',
|
|
187
|
+
},
|
|
188
|
+
}));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (req.method === 'GET' && url.pathname === '/api/shell/sessions/provider-output') {
|
|
193
|
+
providerOutputReads.push({
|
|
194
|
+
provider: url.searchParams.get('provider'),
|
|
195
|
+
projectPath: url.searchParams.get('projectPath'),
|
|
196
|
+
launchId: url.searchParams.get('launchId'),
|
|
197
|
+
});
|
|
198
|
+
const outputs = [
|
|
199
|
+
{
|
|
200
|
+
active: true,
|
|
201
|
+
provider: 'codex',
|
|
202
|
+
projectPath: '/root/pixcode',
|
|
203
|
+
terminalState: 'busy',
|
|
204
|
+
output: 'OpenAI Codex\n› /init\n\n• Working (10s • esc to interrupt)\n',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
active: true,
|
|
208
|
+
provider: 'codex',
|
|
209
|
+
projectPath: '/root/pixcode',
|
|
210
|
+
terminalState: 'busy',
|
|
211
|
+
output: 'OpenAI Codex\n› /init\n\n• Ran npm test\n• Working (30s • esc to interrupt)\n',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
active: true,
|
|
215
|
+
provider: 'codex',
|
|
216
|
+
projectPath: '/root/pixcode',
|
|
217
|
+
terminalState: 'idle',
|
|
218
|
+
output: 'OpenAI Codex\n› /init\n\n• Ran npm test\n\n› Implement {feature}\n',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
active: true,
|
|
222
|
+
provider: 'codex',
|
|
223
|
+
projectPath: '/root/pixcode',
|
|
224
|
+
terminalState: 'idle',
|
|
225
|
+
output: 'Baseline check passed: npm test reports 195 passing, 0 failing.\n\n› Use /skills to list available skills\n',
|
|
226
|
+
},
|
|
227
|
+
];
|
|
228
|
+
res.end(JSON.stringify(outputs[Math.min(providerOutputReads.length - 1, outputs.length - 1)]));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (req.method === 'POST' && url.pathname === '/api/shell/sessions/provider-input') {
|
|
233
|
+
const body = await readJson(req);
|
|
234
|
+
providerInputWrites.push(body);
|
|
235
|
+
res.end(JSON.stringify({ ok: true, wrote: true, provider: body.provider, launchId: body.launchId }));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
res.statusCode = 404;
|
|
240
|
+
res.end(JSON.stringify({ error: url.pathname }));
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
function listen() {
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
server.listen(0, '127.0.0.1', () => {
|
|
246
|
+
const address = server.address();
|
|
247
|
+
resolve(`http://127.0.0.1:${address.port}`);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function callMcp(child, method, params = undefined) {
|
|
253
|
+
const id = callMcp.nextId;
|
|
254
|
+
callMcp.nextId += 1;
|
|
255
|
+
const payload = { jsonrpc: '2.0', id, method, ...(params ? { params } : {}) };
|
|
256
|
+
child.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const timeout = setTimeout(() => {
|
|
259
|
+
reject(new Error(`MCP call timed out: ${method}`));
|
|
260
|
+
}, 10000);
|
|
261
|
+
const onLine = (line) => {
|
|
262
|
+
if (!line.trim()) return;
|
|
263
|
+
const message = JSON.parse(line);
|
|
264
|
+
if (message.id !== id) return;
|
|
265
|
+
clearTimeout(timeout);
|
|
266
|
+
child.stdout.off('data', onData);
|
|
267
|
+
if (message.error) {
|
|
268
|
+
reject(new Error(message.error.message));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
resolve(message.result);
|
|
272
|
+
};
|
|
273
|
+
const onData = (chunk) => {
|
|
274
|
+
callMcp.buffer += chunk.toString();
|
|
275
|
+
let index;
|
|
276
|
+
while ((index = callMcp.buffer.indexOf('\n')) !== -1) {
|
|
277
|
+
const line = callMcp.buffer.slice(0, index);
|
|
278
|
+
callMcp.buffer = callMcp.buffer.slice(index + 1);
|
|
279
|
+
onLine(line);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
child.stdout.on('data', onData);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
callMcp.nextId = 1;
|
|
286
|
+
callMcp.buffer = '';
|
|
287
|
+
|
|
288
|
+
const baseUrl = await listen();
|
|
289
|
+
const child = spawn(process.execPath, [path.join(repoRoot, 'scripts/hermes/pixcode-mcp-server.mjs')], {
|
|
290
|
+
cwd: repoRoot,
|
|
291
|
+
env: {
|
|
292
|
+
...process.env,
|
|
293
|
+
PIXCODE_BASE_URL: baseUrl,
|
|
294
|
+
PIXCODE_API_KEY: apiKey,
|
|
295
|
+
PIXCODE_MCP_READBACK_IDLE_STABLE_MS: '500',
|
|
296
|
+
},
|
|
297
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
await callMcp(child, 'initialize', { protocolVersion: '2024-11-05' });
|
|
302
|
+
const tools = await callMcp(child, 'tools/list');
|
|
303
|
+
const toolNames = tools.tools.map((tool) => tool.name);
|
|
304
|
+
assert(toolNames.includes('pixcode_list_projects'), 'list projects tool missing');
|
|
305
|
+
assert(toolNames.includes('pixcode_get_provider_status'), 'provider status tool missing');
|
|
306
|
+
assert(toolNames.includes('pixcode_get_hermes_gateway_status'), 'Hermes gateway status tool missing');
|
|
307
|
+
assert(toolNames.includes('pixcode_probe_hermes_gateway'), 'Hermes gateway probe tool missing');
|
|
308
|
+
assert(toolNames.includes('pixcode_get_hermes_diagnostics'), 'Hermes diagnostics tool missing');
|
|
309
|
+
assert(toolNames.includes('pixcode_get_hermes_control_plane'), 'Hermes control-plane snapshot tool missing');
|
|
310
|
+
assert(toolNames.includes('pixcode_repair_hermes_control_plane'), 'Hermes control-plane repair tool missing');
|
|
311
|
+
assert(toolNames.includes('pixcode_get_api_manifest'), 'Pixcode API manifest tool missing');
|
|
312
|
+
assert(toolNames.includes('pixcode_api_request'), 'Pixcode generic API request tool missing');
|
|
313
|
+
assert(toolNames.includes('pixcode_hermes_gateway_request'), 'Hermes gateway request proxy tool missing');
|
|
314
|
+
assert(toolNames.includes('pixcode_manage_hermes_cron'), 'Hermes cron management tool missing');
|
|
315
|
+
assert(toolNames.includes('pixcode_send_cli_input'), 'Provider terminal input tool missing');
|
|
316
|
+
|
|
317
|
+
const projects = await callMcp(child, 'tools/call', { name: 'pixcode_list_projects', arguments: {} });
|
|
318
|
+
assert.match(projects.content[0].text, /224/, 'projects response should include file count');
|
|
319
|
+
|
|
320
|
+
const status = await callMcp(child, 'tools/call', { name: 'pixcode_get_hermes_gateway_status', arguments: {} });
|
|
321
|
+
assert.match(status.content[0].text, /127\.0\.0\.1:8642/, 'gateway status should return REST base URL');
|
|
322
|
+
|
|
323
|
+
const probe = await callMcp(child, 'tools/call', {
|
|
324
|
+
name: 'pixcode_probe_hermes_gateway',
|
|
325
|
+
arguments: { projectPath: '/root/pixcode' },
|
|
326
|
+
});
|
|
327
|
+
assert.match(probe.content[0].text, /"ok": true/, 'gateway probe should return ok');
|
|
328
|
+
|
|
329
|
+
const diagnostics = await callMcp(child, 'tools/call', {
|
|
330
|
+
name: 'pixcode_get_hermes_diagnostics',
|
|
331
|
+
arguments: { projectPath: '/root/pixcode' },
|
|
332
|
+
});
|
|
333
|
+
assert.match(diagnostics.content[0].text, /mcp-pixcode/, 'Hermes diagnostics should expose active toolsets.');
|
|
334
|
+
assert.match(diagnostics.content[0].text, /"toolsetAvailable": true/, 'Hermes diagnostics should expose cron toolset availability.');
|
|
335
|
+
|
|
336
|
+
const controlPlane = await callMcp(child, 'tools/call', {
|
|
337
|
+
name: 'pixcode_get_hermes_control_plane',
|
|
338
|
+
arguments: { projectPath: '/root/pixcode' },
|
|
339
|
+
});
|
|
340
|
+
assert.match(controlPlane.content[0].text, /Hermes REST gateway/, 'Hermes control-plane tool should expose capability state.');
|
|
341
|
+
assert.match(controlPlane.content[0].text, /"pixcodeMcpReady": true/, 'Hermes control-plane tool should expose MCP readiness.');
|
|
342
|
+
|
|
343
|
+
const repairedControlPlane = await callMcp(child, 'tools/call', {
|
|
344
|
+
name: 'pixcode_repair_hermes_control_plane',
|
|
345
|
+
arguments: { projectPath: '/root/pixcode', forceRestart: true },
|
|
346
|
+
});
|
|
347
|
+
assert.match(repairedControlPlane.content[0].text, /"ok": true/, 'Hermes control-plane repair tool should return repaired state.');
|
|
348
|
+
|
|
349
|
+
const manifest = await callMcp(child, 'tools/call', { name: 'pixcode_get_api_manifest', arguments: {} });
|
|
350
|
+
assert.match(manifest.content[0].text, /Pixcode Public API/, 'API manifest tool should expose Pixcode API docs to Hermes.');
|
|
351
|
+
|
|
352
|
+
const apiRequest = await callMcp(child, 'tools/call', {
|
|
353
|
+
name: 'pixcode_api_request',
|
|
354
|
+
arguments: { method: 'GET', path: '/api/projects' },
|
|
355
|
+
});
|
|
356
|
+
assert.match(apiRequest.content[0].text, /"name": "pixcode"/, 'generic Pixcode API tool should call allowlisted local API paths.');
|
|
357
|
+
|
|
358
|
+
const cronJobs = await callMcp(child, 'tools/call', {
|
|
359
|
+
name: 'pixcode_hermes_gateway_request',
|
|
360
|
+
arguments: { method: 'GET', endpoint: '/api/jobs', projectPath: '/root/pixcode' },
|
|
361
|
+
});
|
|
362
|
+
assert.match(cronJobs.content[0].text, /Morning repo check/, 'Hermes gateway request tool should expose API-server jobs/cron endpoints.');
|
|
363
|
+
assert.equal(gatewayRequests[0].endpoint, '/api/jobs', 'Gateway request should keep the requested Hermes endpoint.');
|
|
364
|
+
|
|
365
|
+
const cronList = await callMcp(child, 'tools/call', {
|
|
366
|
+
name: 'pixcode_manage_hermes_cron',
|
|
367
|
+
arguments: { action: 'list', projectPath: '/root/pixcode' },
|
|
368
|
+
});
|
|
369
|
+
assert.match(cronList.content[0].text, /Morning repo check/, 'Hermes cron helper should list managed Hermes jobs.');
|
|
370
|
+
|
|
371
|
+
const launch = await callMcp(child, 'tools/call', {
|
|
372
|
+
name: 'pixcode_open_cli_terminal',
|
|
373
|
+
arguments: { provider: 'codex', projectPath: '/root/pixcode', prompt: 'smoke' },
|
|
374
|
+
});
|
|
375
|
+
assert.match(launch.content[0].text, /hermes-mcp/, 'terminal launch should roundtrip through Pixcode API');
|
|
376
|
+
assert.match(launch.content[0].text, /"pixcodeMcpConfigured": true/, 'terminal launch should configure Pixcode MCP for the selected provider first');
|
|
377
|
+
assert.equal(providerMcpUpserts.length, 1, 'Codex launch should upsert a project-scoped Pixcode MCP server');
|
|
378
|
+
assert.equal(providerMcpUpserts[0].name, 'pixcode', 'Provider MCP server should be named pixcode');
|
|
379
|
+
|
|
380
|
+
providerOutputReads.length = 0;
|
|
381
|
+
const launchWithReadback = await callMcp(child, 'tools/call', {
|
|
382
|
+
name: 'pixcode_open_cli_terminal',
|
|
383
|
+
arguments: {
|
|
384
|
+
provider: 'codex',
|
|
385
|
+
projectPath: '/root/pixcode',
|
|
386
|
+
prompt: 'read final output',
|
|
387
|
+
startupInput: '/init',
|
|
388
|
+
waitForOutputMs: 7000,
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
assert(
|
|
392
|
+
providerOutputReads.length >= 5,
|
|
393
|
+
`readback should keep polling until the provider terminal is idle, reads=${providerOutputReads.length}`,
|
|
394
|
+
);
|
|
395
|
+
assert.match(
|
|
396
|
+
launchWithReadback.content[0].text,
|
|
397
|
+
/195 passing, 0 failing/,
|
|
398
|
+
'readback should return the final Codex output instead of the first working frame',
|
|
399
|
+
);
|
|
400
|
+
assert(
|
|
401
|
+
providerOutputReads.every((read) => read.launchId === '2'),
|
|
402
|
+
`readback should be tied to the Hermes terminal launch id, reads=${JSON.stringify(providerOutputReads)}`,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
const blockedLaunch = await callMcp(child, 'tools/call', {
|
|
406
|
+
name: 'pixcode_open_cli_terminal',
|
|
407
|
+
arguments: { provider: 'qwen', projectPath: '/root/pixcode', prompt: 'smoke' },
|
|
408
|
+
});
|
|
409
|
+
assert.match(blockedLaunch.content[0].text, /"launched": false/, 'uninstalled providers should not create terminal launches');
|
|
410
|
+
assert.match(blockedLaunch.content[0].text, /"reason": "not_installed"/, 'uninstalled provider response should explain the block');
|
|
411
|
+
assert.equal(terminalLaunches.length, 2, 'Only installed Codex provider launches should be created');
|
|
412
|
+
|
|
413
|
+
const inputWrite = await callMcp(child, 'tools/call', {
|
|
414
|
+
name: 'pixcode_send_cli_input',
|
|
415
|
+
arguments: { provider: 'codex', projectPath: '/root/pixcode', input: 'selam', submit: true, launchId: 2 },
|
|
416
|
+
});
|
|
417
|
+
assert.match(inputWrite.content[0].text, /"wrote": true/, 'MCP should be able to submit input to an existing visible provider terminal.');
|
|
418
|
+
assert.equal(providerInputWrites[0].input, 'selam', 'Provider input body should preserve exact user input.');
|
|
419
|
+
assert.equal(providerInputWrites[0].submit, true, 'Provider input should submit by default when requested.');
|
|
420
|
+
|
|
421
|
+
assert(seen.every((entry) => entry.auth === `Bearer ${apiKey}`), 'all MCP calls should use the Pixcode bearer key');
|
|
422
|
+
console.log('hermes MCP Pixcode roundtrip smoke passed');
|
|
423
|
+
} finally {
|
|
424
|
+
child.kill();
|
|
425
|
+
await new Promise((resolve) => server.close(resolve));
|
|
426
|
+
}
|