feat(ql3): add read-only cluster copilot console

This commit is contained in:
whyour
2026-08-16 04:35:28 +08:00
parent da4e7cf688
commit c4a1238a92
32 changed files with 3407 additions and 20 deletions
@@ -0,0 +1,141 @@
import { createHash } from 'node:crypto';
import {
lstatSync,
readFileSync,
realpathSync,
type PathLike,
} from 'node:fs';
import { isAbsolute, relative, resolve, sep } from 'node:path';
import { TextDecoder } from 'node:util';
export interface ClusterCopilotConsoleAssets {
readonly html: string;
readonly css: string;
readonly javascript: string;
}
export class ClusterCopilotConsoleAssetError extends Error {
readonly code = 'QL3_CLUSTER_COPILOT_CONSOLE_ASSET_INVALID';
constructor() {
super('Cluster Copilot Console asset is invalid');
this.name = 'ClusterCopilotConsoleAssetError';
}
}
const ASSETS = Object.freeze([
Object.freeze({
name: 'index.html',
field: 'html',
maximumBytes: 32 * 1024,
digest: 'f9fa959f30b92c6b000eecb744ce1d0a7fce822c62b3e17dcf10d4d579a072ac',
}),
Object.freeze({
name: 'app.css',
field: 'css',
maximumBytes: 64 * 1024,
digest: '200c3405e1e12329fcfb50509b31b19f1567a91552865f039ce0c2de1530032c',
}),
Object.freeze({
name: 'app.js',
field: 'javascript',
maximumBytes: 32 * 1024,
digest: 'd60913e725e767d9fa2cb65d60c0eae6d75d219f4bec8aad166bed8b6507fe02',
}),
] as const);
function invalid(): never {
throw new ClusterCopilotConsoleAssetError();
}
function inside(parent: string, candidate: string): boolean {
const pathFromParent = relative(parent, candidate);
return (
pathFromParent !== '' &&
pathFromParent !== '..' &&
!pathFromParent.startsWith('..' + sep) &&
!isAbsolute(pathFromParent)
);
}
function readAsset(
assetRoot: string,
name: string,
maximumBytes: number,
digest: string,
): string {
const candidate = resolve(assetRoot, name);
const status = lstatSync(candidate, { throwIfNoEntry: false });
if (
status === undefined ||
!status.isFile() ||
status.isSymbolicLink() ||
status.size < 1 ||
status.size > maximumBytes
) {
return invalid();
}
const canonicalRoot = realpathSync(assetRoot);
const canonicalCandidate = realpathSync(candidate);
if (
!inside(canonicalRoot, canonicalCandidate) ||
canonicalCandidate !== resolve(canonicalRoot, name)
) {
return invalid();
}
let bytes: Buffer | undefined;
try {
bytes = readFileSync(candidate as PathLike);
if (
bytes.byteLength !== status.size ||
createHash('sha256').update(bytes).digest('hex') !== digest ||
bytes.includes(0)
) {
return invalid();
}
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
} catch (error) {
if (error instanceof ClusterCopilotConsoleAssetError) throw error;
return invalid();
} finally {
bytes?.fill(0);
}
}
export function loadClusterCopilotConsoleAssets(
moduleDirectory: string,
): Readonly<ClusterCopilotConsoleAssets> {
if (typeof moduleDirectory !== 'string' || !isAbsolute(moduleDirectory)) {
return invalid();
}
const packageRoot = resolve(moduleDirectory, '..', '..');
const assetRoot = resolve(packageRoot, 'assets', 'copilot-console');
const packageStatus = lstatSync(packageRoot, { throwIfNoEntry: false });
const assetStatus = lstatSync(assetRoot, { throwIfNoEntry: false });
if (
packageStatus === undefined ||
!packageStatus.isDirectory() ||
packageStatus.isSymbolicLink() ||
assetStatus === undefined ||
!assetStatus.isDirectory() ||
assetStatus.isSymbolicLink() ||
realpathSync(assetRoot) !==
resolve(realpathSync(packageRoot), 'assets', 'copilot-console')
) {
return invalid();
}
const result: Record<string, string> = {};
for (const asset of ASSETS) {
result[asset.field] = readAsset(
assetRoot,
asset.name,
asset.maximumBytes,
asset.digest,
);
}
return Object.freeze({
html: result.html!,
css: result.css!,
javascript: result.javascript!,
});
}
@@ -0,0 +1,245 @@
#!/usr/bin/env node
import {
executeClusterCopilotCommand,
probeClusterCopilotClientReadiness,
validateClusterCopilotClientConfiguration,
validateClusterCopilotClientCredentialFile,
type ClusterCopilotClientCommand,
} from '../copilot-client/client';
import { readCanonicalFile } from '../management-support/managementClientConfiguration';
import { loadClusterCopilotConsoleAssets } from './assets';
import {
clusterCopilotConsoleSessionDigest,
startClusterCopilotConsoleServer,
} from './server';
const USAGE = [
'Usage:',
' ql3-copilot-console --config /absolute/client.json --credential /absolute/credential --session /absolute/session [--port=0..65535]',
' ql3-copilot-console --check --config /absolute/client.json --credential /absolute/credential --session /absolute/session',
'',
'The Console binds only 127.0.0.1 and exposes inspect/output reads.',
'The browser session key remains in a separate owner-private 0600 file.',
].join('\n');
interface ClusterCopilotConsoleCliArguments {
readonly check: boolean;
readonly configFile: string;
readonly credentialFile: string;
readonly sessionFile: string;
readonly port: number;
}
const SESSION_TOKEN = /^[A-Za-z0-9_-]{43}$/;
const MAXIMUM_SESSION_BYTES = 128;
function usageFailure(): never {
process.stderr.write(USAGE + '\n');
process.exit(64);
}
function argumentValue(
argv: readonly string[],
index: number,
name: string,
): Readonly<{ value: string; consumed: number }> | null {
const current = argv[index];
if (current === name) {
const next = argv[index + 1];
if (typeof next !== 'string' || next === '' || next.startsWith('--')) {
return usageFailure();
}
return Object.freeze({ value: next, consumed: 2 });
}
const prefix = name + '=';
if (current?.startsWith(prefix) && current.length > prefix.length) {
return Object.freeze({
value: current.slice(prefix.length),
consumed: 1,
});
}
return null;
}
export function parseClusterCopilotConsoleCliArguments(
argv: readonly string[],
): Readonly<ClusterCopilotConsoleCliArguments> {
let check = false;
let configFile: string | undefined;
let credentialFile: string | undefined;
let sessionFile: string | undefined;
let port = 0;
let portSeen = false;
for (let index = 0; index < argv.length; ) {
const current = argv[index];
if (current === '--check' && !check) {
check = true;
index += 1;
continue;
}
const config = argumentValue(argv, index, '--config');
if (config) {
if (configFile !== undefined) return usageFailure();
configFile = config.value;
index += config.consumed;
continue;
}
const credential = argumentValue(argv, index, '--credential');
if (credential) {
if (credentialFile !== undefined) return usageFailure();
credentialFile = credential.value;
index += credential.consumed;
continue;
}
const session = argumentValue(argv, index, '--session');
if (session) {
if (sessionFile !== undefined) return usageFailure();
sessionFile = session.value;
index += session.consumed;
continue;
}
const portArgument = argumentValue(argv, index, '--port');
if (portArgument) {
if (portSeen || !/^(?:0|[1-9][0-9]{0,4})$/.test(portArgument.value)) {
return usageFailure();
}
portSeen = true;
port = Number(portArgument.value);
if (
!Number.isSafeInteger(port) ||
(port !== 0 && (port < 1_024 || port > 65_535))
) {
return usageFailure();
}
index += portArgument.consumed;
continue;
}
return usageFailure();
}
if (
configFile === undefined ||
credentialFile === undefined ||
sessionFile === undefined ||
(check && port !== 0)
) {
return usageFailure();
}
return Object.freeze({
check,
configFile,
credentialFile,
sessionFile,
port,
});
}
function readSessionDigest(sessionFile: string): Buffer {
let bytes: Buffer | undefined;
try {
bytes = readCanonicalFile(
sessionFile,
MAXIMUM_SESSION_BYTES,
'private',
);
if (
bytes.some((byte) => byte > 0x7f) ||
!SESSION_TOKEN.test(bytes.toString('ascii'))
) {
throw new Error('invalid session token');
}
return clusterCopilotConsoleSessionDigest(bytes.toString('ascii'));
} finally {
bytes?.fill(0);
}
}
async function main(): Promise<void> {
if (
process.argv.length === 3 &&
(process.argv[2] === '--help' || process.argv[2] === '-h')
) {
process.stdout.write(USAGE + '\n');
return;
}
const parsed = parseClusterCopilotConsoleCliArguments(process.argv.slice(2));
const assets = loadClusterCopilotConsoleAssets(__dirname);
validateClusterCopilotClientConfiguration(parsed.configFile);
validateClusterCopilotClientCredentialFile(parsed.credentialFile);
const sessionDigest = readSessionDigest(parsed.sessionFile);
if (parsed.check) {
try {
const readiness = await probeClusterCopilotClientReadiness(
parsed.configFile,
);
process.stdout.write(
JSON.stringify({
schemaVersion: 1,
component: 'qinglong3-cluster-copilot-console',
event: 'preflight_checked',
ready: readiness.ready,
listenAddress: '127.0.0.1',
browserCredential: 'forbidden',
clusterCredential: 'server_only',
operations: ['inspect', 'output'],
mutation: false,
}) + '\n',
);
if (!readiness.ready) process.exitCode = 69;
return;
} finally {
sessionDigest.fill(0);
}
}
const server = await startClusterCopilotConsoleServer({
assets,
executor: Object.freeze({
execute(command: Readonly<ClusterCopilotClientCommand>) {
return executeClusterCopilotCommand({
configFile: parsed.configFile,
credentialFile: parsed.credentialFile,
command,
});
},
}),
port: parsed.port,
sessionDigest,
});
sessionDigest.fill(0);
process.stdout.write(
JSON.stringify({
schemaVersion: 1,
component: 'qinglong3-cluster-copilot-console',
event: 'started',
origin: server.origin,
listenAddress: '127.0.0.1',
browserCredential: 'forbidden',
clusterCredential: 'server_only',
operations: ['inspect', 'output'],
mutation: false,
}) + '\n',
);
await new Promise<void>((resolve) => {
let stopping = false;
const stop = (): void => {
if (stopping) return;
stopping = true;
void server.close().finally(resolve);
};
process.once('SIGINT', stop);
process.once('SIGTERM', stop);
});
}
void main().catch(() => {
process.stderr.write(
JSON.stringify({
schemaVersion: 1,
component: 'qinglong3-cluster-copilot-console',
event: 'process_failed',
}) + '\n',
);
process.exitCode = 1;
});
@@ -0,0 +1,86 @@
import {
CLUSTER_COPILOT_CLIENT_COMMAND_SCHEMA,
type ClusterCopilotClientCommand,
} from '../copilot-client/contracts';
export const CLUSTER_COPILOT_CONSOLE_READ_REQUEST_SCHEMA =
'qinglong/cluster-copilot-console-read-request@v1' as const;
export const CLUSTER_COPILOT_CONSOLE_READ_RESPONSE_SCHEMA =
'qinglong/cluster-copilot-console-read-response@v1' as const;
export type ClusterCopilotConsoleReadOperation = 'inspect' | 'output';
export interface ClusterCopilotConsoleReadRequest {
readonly schema: typeof CLUSTER_COPILOT_CONSOLE_READ_REQUEST_SCHEMA;
readonly operation: ClusterCopilotConsoleReadOperation;
readonly projectId: string;
readonly sourceRunId: string;
readonly requestId: string;
}
export class InvalidClusterCopilotConsoleReadRequestError extends TypeError {
readonly code = 'QL3_CLUSTER_COPILOT_CONSOLE_READ_REQUEST_INVALID';
constructor() {
super('Cluster Copilot Console read request is invalid');
this.name = 'InvalidClusterCopilotConsoleReadRequestError';
}
}
const IDENTITY = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
const RUN_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,35}$/;
function invalid(): never {
throw new InvalidClusterCopilotConsoleReadRequestError();
}
export function normalizeClusterCopilotConsoleReadRequest(
value: unknown,
): Readonly<ClusterCopilotConsoleReadRequest> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return invalid();
}
const record = value as Record<string, unknown>;
const keys = Object.keys(record).sort();
const expected = [
'operation',
'projectId',
'requestId',
'schema',
'sourceRunId',
];
if (
keys.length !== expected.length ||
keys.some((key, index) => key !== expected[index]) ||
record.schema !== CLUSTER_COPILOT_CONSOLE_READ_REQUEST_SCHEMA ||
(record.operation !== 'inspect' && record.operation !== 'output') ||
typeof record.projectId !== 'string' ||
!IDENTITY.test(record.projectId) ||
typeof record.sourceRunId !== 'string' ||
!RUN_ID.test(record.sourceRunId) ||
typeof record.requestId !== 'string' ||
!IDENTITY.test(record.requestId)
) {
return invalid();
}
return Object.freeze({
schema: CLUSTER_COPILOT_CONSOLE_READ_REQUEST_SCHEMA,
operation: record.operation,
projectId: record.projectId,
sourceRunId: record.sourceRunId,
requestId: record.requestId,
});
}
export function clusterCopilotConsoleClientCommand(
request: Readonly<ClusterCopilotConsoleReadRequest>,
): Readonly<ClusterCopilotClientCommand> {
const normalized = normalizeClusterCopilotConsoleReadRequest(request);
return Object.freeze({
schema: CLUSTER_COPILOT_CLIENT_COMMAND_SCHEMA,
operation: normalized.operation,
projectId: normalized.projectId,
sourceRunId: normalized.sourceRunId,
requestId: normalized.requestId,
});
}
@@ -0,0 +1,498 @@
import { createHash, timingSafeEqual } from 'node:crypto';
import {
createServer,
type IncomingMessage,
type ServerResponse,
} from 'node:http';
import {
ClusterCopilotClientConfigurationError,
ClusterCopilotClientRemoteError,
ClusterCopilotClientRequestError,
type ClusterCopilotClientCommand,
type ClusterCopilotClientResult,
} from '../copilot-client/client';
import {
type ClusterCopilotConsoleAssets,
} from './assets';
import {
CLUSTER_COPILOT_CONSOLE_READ_RESPONSE_SCHEMA,
InvalidClusterCopilotConsoleReadRequestError,
clusterCopilotConsoleClientCommand,
normalizeClusterCopilotConsoleReadRequest,
} from './contracts';
export const CLUSTER_COPILOT_CONSOLE_LIMITS = Object.freeze({
maximumBodyBytes: 4 * 1024,
maximumResponseBytes: 2 * 1024 * 1024 + 4 * 1024,
maximumConcurrentRequests: 2,
maximumConnections: 16,
shutdownTimeoutMs: 2_000,
});
export interface ClusterCopilotConsoleExecutor {
execute(
command: Readonly<ClusterCopilotClientCommand>,
): Promise<Readonly<ClusterCopilotClientResult>>;
}
export interface ClusterCopilotConsoleServerOptions {
readonly assets: Readonly<ClusterCopilotConsoleAssets>;
readonly executor: ClusterCopilotConsoleExecutor;
readonly port: number;
readonly sessionDigest: Buffer;
}
export interface ClusterCopilotConsoleServer {
readonly origin: string;
close(): Promise<void>;
}
export class ClusterCopilotConsoleConfigurationError extends TypeError {
readonly code = 'QL3_CLUSTER_COPILOT_CONSOLE_CONFIG_INVALID';
constructor() {
super('Cluster Copilot Console configuration is invalid');
this.name = 'ClusterCopilotConsoleConfigurationError';
}
}
const SESSION_TOKEN = /^[A-Za-z0-9_-]{43}$/;
const SESSION_DIGEST_DOMAIN = Buffer.from(
'qinglong-cluster-copilot-console-session-v1\0',
'utf8',
);
const CONTENT_SECURITY_POLICY = [
"default-src 'none'",
"base-uri 'none'",
"connect-src 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"script-src 'self'",
"style-src 'self'",
"img-src 'none'",
"font-src 'none'",
"object-src 'none'",
"media-src 'none'",
"manifest-src 'none'",
"worker-src 'none'",
].join('; ');
function invalid(): never {
throw new ClusterCopilotConsoleConfigurationError();
}
function exactObject(
value: unknown,
keys: readonly string[],
): Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return invalid();
}
const record = value as Record<string, unknown>;
const actual = Object.keys(record).sort();
const expected = [...keys].sort();
if (
actual.length !== expected.length ||
actual.some((key, index) => key !== expected[index])
) {
return invalid();
}
return record;
}
export function clusterCopilotConsoleSessionDigest(value: string): Buffer {
if (typeof value !== 'string' || !SESSION_TOKEN.test(value)) {
return invalid();
}
const decoded = Buffer.from(value, 'base64url');
if (
decoded.byteLength !== 32 ||
decoded.toString('base64url') !== value
) {
decoded.fill(0);
return invalid();
}
decoded.fill(0);
return createHash('sha256')
.update(SESSION_DIGEST_DOMAIN)
.update(value, 'ascii')
.digest();
}
function securityHeaders(contentType: string): Readonly<Record<string, string>> {
return Object.freeze({
'cache-control': 'no-store',
'content-security-policy': CONTENT_SECURITY_POLICY,
'content-type': contentType,
'cross-origin-opener-policy': 'same-origin',
'cross-origin-resource-policy': 'same-origin',
'origin-agent-cluster': '?1',
'permissions-policy':
'camera=(), display-capture=(), geolocation=(), microphone=(), payment=(), usb=()',
'referrer-policy': 'no-referrer',
'x-content-type-options': 'nosniff',
'x-frame-options': 'DENY',
});
}
function send(
response: ServerResponse,
statusCode: number,
contentType: string,
body: string,
extraHeaders: Readonly<Record<string, string>> = {},
): void {
const bytes = Buffer.from(body, 'utf8');
response.writeHead(statusCode, {
...securityHeaders(contentType),
...extraHeaders,
connection: 'close',
'content-length': String(bytes.byteLength),
});
response.end(bytes, () => bytes.fill(0));
}
function sendJson(
response: ServerResponse,
statusCode: number,
body: Readonly<Record<string, unknown>>,
extraHeaders: Readonly<Record<string, string>> = {},
): void {
send(
response,
statusCode,
'application/json; charset=utf-8',
JSON.stringify(body),
extraHeaders,
);
}
function headerCount(request: IncomingMessage, name: string): number {
let count = 0;
for (let index = 0; index < request.rawHeaders.length; index += 2) {
if (request.rawHeaders[index]?.toLowerCase() === name) count += 1;
}
return count;
}
function targetPath(request: IncomingMessage): 'inspect' | 'output' | null {
if (request.method !== 'POST') return null;
if (request.url === '/api/v1/copilot/inspect') return 'inspect';
if (request.url === '/api/v1/copilot/output') return 'output';
return null;
}
function authorize(
request: IncomingMessage,
expectedOrigin: string,
sessionDigest: Buffer,
): boolean {
if (
headerCount(request, 'authorization') !== 1 ||
headerCount(request, 'origin') !== 1 ||
request.headers.origin !== expectedOrigin ||
request.headers.host !== expectedOrigin.slice('http://'.length)
) {
return false;
}
const authorization = request.headers.authorization;
if (
typeof authorization !== 'string' ||
!authorization.startsWith('QL3-Console ')
) {
return false;
}
let candidate: Buffer | undefined;
try {
candidate = clusterCopilotConsoleSessionDigest(
authorization.slice('QL3-Console '.length),
);
return timingSafeEqual(candidate, sessionDigest);
} catch {
return false;
} finally {
candidate?.fill(0);
}
}
async function readJsonBody(request: IncomingMessage): Promise<unknown> {
if (
headerCount(request, 'content-type') !== 1 ||
headerCount(request, 'content-length') !== 1 ||
request.headers['content-type'] !== 'application/json; charset=utf-8' ||
request.headers['content-encoding'] !== undefined ||
request.headers['transfer-encoding'] !== undefined ||
typeof request.headers['content-length'] !== 'string' ||
!/^[1-9][0-9]*$/.test(request.headers['content-length'])
) {
throw new InvalidClusterCopilotConsoleReadRequestError();
}
const expectedLength = Number(request.headers['content-length']);
if (
!Number.isSafeInteger(expectedLength) ||
expectedLength < 2 ||
expectedLength > CLUSTER_COPILOT_CONSOLE_LIMITS.maximumBodyBytes
) {
throw new InvalidClusterCopilotConsoleReadRequestError();
}
const chunks: Buffer[] = [];
let length = 0;
try {
for await (const chunk of request) {
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
length += bytes.byteLength;
if (
length > expectedLength ||
length > CLUSTER_COPILOT_CONSOLE_LIMITS.maximumBodyBytes
) {
throw new InvalidClusterCopilotConsoleReadRequestError();
}
chunks.push(bytes);
}
if (request.aborted || length !== expectedLength) {
throw new InvalidClusterCopilotConsoleReadRequestError();
}
const body = Buffer.concat(chunks, length);
try {
return JSON.parse(body.toString('utf8'));
} finally {
body.fill(0);
}
} catch (error) {
if (error instanceof InvalidClusterCopilotConsoleReadRequestError) {
throw error;
}
throw new InvalidClusterCopilotConsoleReadRequestError();
} finally {
for (const chunk of chunks) chunk.fill(0);
}
}
function remoteFailure(
response: ServerResponse,
error: ClusterCopilotClientRemoteError,
): void {
const statusCode =
error.statusCode === 404
? 404
: error.statusCode === 429
? 429
: 502;
sendJson(
response,
statusCode,
Object.freeze({
schema: CLUSTER_COPILOT_CONSOLE_READ_RESPONSE_SCHEMA,
code: error.responseCode,
requestId: error.requestId,
retryAfterSeconds: error.retryAfterSeconds,
}),
error.retryAfterSeconds === null
? {}
: { 'retry-after': String(error.retryAfterSeconds) },
);
}
export async function startClusterCopilotConsoleServer(
options: ClusterCopilotConsoleServerOptions,
): Promise<Readonly<ClusterCopilotConsoleServer>> {
const record = exactObject(options, [
'assets',
'executor',
'port',
'sessionDigest',
]);
const assets = exactObject(record.assets, ['css', 'html', 'javascript']);
if (
typeof assets.html !== 'string' ||
assets.html.length < 1 ||
typeof assets.css !== 'string' ||
assets.css.length < 1 ||
typeof assets.javascript !== 'string' ||
assets.javascript.length < 1 ||
!record.executor ||
typeof (record.executor as ClusterCopilotConsoleExecutor).execute !==
'function' ||
!Number.isSafeInteger(record.port) ||
((record.port as number) !== 0 &&
((record.port as number) < 1_024 || (record.port as number) > 65_535)) ||
!Buffer.isBuffer(record.sessionDigest) ||
(record.sessionDigest as Buffer).byteLength !== 32
) {
return invalid();
}
const sessionDigest = Buffer.from(record.sessionDigest as Buffer);
const executor = record.executor as ClusterCopilotConsoleExecutor;
let expectedOrigin = '';
let inFlight = 0;
let closed = false;
const server = createServer(async (request, response) => {
response.shouldKeepAlive = false;
const hostMatches =
expectedOrigin !== '' &&
request.headers.host === expectedOrigin.slice('http://'.length);
if (request.method === 'GET' && hostMatches) {
if (request.url === '/') {
send(response, 200, 'text/html; charset=utf-8', assets.html as string);
return;
}
if (request.url === '/app.css') {
send(response, 200, 'text/css; charset=utf-8', assets.css as string);
return;
}
if (request.url === '/app.js') {
send(
response,
200,
'text/javascript; charset=utf-8',
assets.javascript as string,
);
return;
}
}
const operation = targetPath(request);
if (
!hostMatches ||
operation === null ||
!authorize(request, expectedOrigin, sessionDigest)
) {
sendJson(response, 404, Object.freeze({ code: 'not_found' }));
request.resume();
return;
}
if (
inFlight >= CLUSTER_COPILOT_CONSOLE_LIMITS.maximumConcurrentRequests
) {
sendJson(
response,
429,
Object.freeze({
schema: CLUSTER_COPILOT_CONSOLE_READ_RESPONSE_SCHEMA,
code: 'cluster_copilot_console_busy',
}),
{ 'retry-after': '1' },
);
request.resume();
return;
}
inFlight += 1;
try {
const body = await readJsonBody(request);
const normalized = normalizeClusterCopilotConsoleReadRequest(body);
if (normalized.operation !== operation) {
throw new InvalidClusterCopilotConsoleReadRequestError();
}
const result = await executor.execute(
clusterCopilotConsoleClientCommand(normalized),
);
const envelope = Object.freeze({
schema: CLUSTER_COPILOT_CONSOLE_READ_RESPONSE_SCHEMA,
operation,
requestId: result.requestId,
result,
});
const encoded = JSON.stringify(envelope);
if (
Buffer.byteLength(encoded, 'utf8') >
CLUSTER_COPILOT_CONSOLE_LIMITS.maximumResponseBytes
) {
throw new ClusterCopilotClientRequestError();
}
send(
response,
200,
'application/json; charset=utf-8',
encoded,
);
} catch (error) {
if (response.headersSent) {
response.destroy();
} else if (
error instanceof InvalidClusterCopilotConsoleReadRequestError
) {
sendJson(
response,
400,
Object.freeze({
schema: CLUSTER_COPILOT_CONSOLE_READ_RESPONSE_SCHEMA,
code: 'invalid_cluster_copilot_console_read_request',
}),
);
} else if (error instanceof ClusterCopilotClientRemoteError) {
remoteFailure(response, error);
} else if (
error instanceof ClusterCopilotClientConfigurationError ||
error instanceof ClusterCopilotClientRequestError
) {
sendJson(
response,
503,
Object.freeze({
schema: CLUSTER_COPILOT_CONSOLE_READ_RESPONSE_SCHEMA,
code: 'cluster_copilot_console_upstream_unavailable',
}),
);
} else {
sendJson(
response,
503,
Object.freeze({
schema: CLUSTER_COPILOT_CONSOLE_READ_RESPONSE_SCHEMA,
code: 'cluster_copilot_console_unavailable',
}),
);
}
} finally {
inFlight -= 1;
}
});
server.maxConnections = CLUSTER_COPILOT_CONSOLE_LIMITS.maximumConnections;
server.headersTimeout = 5_000;
server.requestTimeout = 5_000;
server.keepAliveTimeout = 1;
server.maxRequestsPerSocket = 1;
server.on('clientError', (_error, socket) => socket.destroy());
try {
await new Promise<void>((resolve, reject) => {
server.once('error', reject);
server.listen(record.port as number, '127.0.0.1', () => {
server.off('error', reject);
resolve();
});
});
const address = server.address();
if (!address || typeof address === 'string') return invalid();
expectedOrigin = 'http://127.0.0.1:' + String(address.port);
} catch (error) {
sessionDigest.fill(0);
server.closeAllConnections();
if (error instanceof ClusterCopilotConsoleConfigurationError) throw error;
throw new ClusterCopilotConsoleConfigurationError();
}
return Object.freeze({
origin: expectedOrigin,
async close(): Promise<void> {
if (closed) return;
closed = true;
await new Promise<void>((resolve) => {
const timeout = setTimeout(() => {
server.closeAllConnections();
}, CLUSTER_COPILOT_CONSOLE_LIMITS.shutdownTimeoutMs);
timeout.unref();
server.close(() => {
clearTimeout(timeout);
resolve();
});
server.closeIdleConnections();
});
sessionDigest.fill(0);
},
});
}
@@ -46,6 +46,12 @@ export const QINGLONG3_CLUSTER_PRODUCT_COMMANDS: readonly QingLong3ClusterProduc
target: 'copilot-mcp/cli.js',
description: 'serve the bounded Cluster Copilot MCP over stdio',
}),
Object.freeze({
name: 'copilot-console',
binary: 'ql3-copilot-console',
target: 'copilot-console/cli.js',
description: 'open the loopback-only read-only Copilot Console',
}),
Object.freeze({
name: 'package',
binary: 'ql3-plugin-package-client',
@@ -192,7 +198,7 @@ export function qingLong3ClusterProductHelp(): string {
'',
'Use `ql3-cluster-admin <command> --help` for command-specific usage.',
'Use `--context=/absolute/operator-context.json` only with remote client commands.',
'Keep the MCP config explicit; it contains stable paths to a separately rotated credential.',
'Keep MCP and Console authority explicit; neither belongs in operator context.',
'Command and short-lived assertion files always remain explicit per invocation.',
'Server, migration, recovery, executor and key-custody authorities remain isolated.',
].join('\n');