mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
Feat/integrate lint preview (#285)
* feat: 适配 zed acp 协议 * docs: 完善 acp 文档 * feat: integrate feature branches + daemon/job 命令层级化 + 跨平台后台引擎 Cherry-picked from origin/lint/preview (637c908), excluding lint-only changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct detectMimeFromBase64 to decode raw bytes from base64 Cherry-picked from origin/lint/preview (ee36954). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: daemon 子进程 spawn 跨平台修复 + CliLaunchSpec 集中化重构 Cherry-picked from origin/lint/preview (c5f52cd), excluding lint-only formatting changes. - 新建 src/utils/cliLaunch.ts: 集中化 CLI 子进程启动层 - 修复 --daemon-worker=kind 等号格式解析 - 修复 daemon/bg fast path 缺少 setShellIfWindows() - 修复 checkPathExists 用 existsSync 替代 execSync('dir') - 7 个 spawn 站点迁移到 CliLaunchSpec Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
10
packages/remote-control-server/src/logger.ts
Normal file
10
packages/remote-control-server/src/logger.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/** Thin logging wrapper — silent in test environment, uses console in production. */
|
||||
const isTest = process.env.NODE_ENV === "test" || (typeof Bun !== "undefined" && !!Bun.env.BUN_TEST);
|
||||
|
||||
export function log(...args: unknown[]): void {
|
||||
if (!isTest) console.log(...args);
|
||||
}
|
||||
|
||||
export function error(...args: unknown[]): void {
|
||||
if (!isTest) console.error(...args);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { log, error as logError } from "../../logger";
|
||||
import { Hono } from "hono";
|
||||
import { createBunWebSocket } from "hono/bun";
|
||||
import { validateApiKey } from "../../auth/api-key";
|
||||
@@ -30,14 +31,14 @@ function authenticateRequest(c: any, label: string, expectedSessionId?: string):
|
||||
const payload = verifyWorkerJwt(token);
|
||||
if (payload) {
|
||||
if (expectedSessionId && payload.session_id !== expectedSessionId) {
|
||||
console.log(`[Auth] ${label}: FAILED — JWT session_id mismatch`);
|
||||
log(`[Auth] ${label}: FAILED — JWT session_id mismatch`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Auth] ${label}: FAILED — no valid API key or JWT`);
|
||||
log(`[Auth] ${label}: FAILED — no valid API key or JWT`);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -85,7 +86,7 @@ app.get(
|
||||
|
||||
const session = getSession(sessionId);
|
||||
if (!session) {
|
||||
console.log(`[WS] Upgrade rejected: session ${sessionId} not found`);
|
||||
log(`[WS] Upgrade rejected: session ${sessionId} not found`);
|
||||
return {
|
||||
onOpen(_evt, ws) {
|
||||
ws.close(4001, "session not found");
|
||||
@@ -93,7 +94,7 @@ app.get(
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`[WS] Upgrade accepted: session=${sessionId}`);
|
||||
log(`[WS] Upgrade accepted: session=${sessionId}`);
|
||||
return {
|
||||
onOpen(_evt, ws) {
|
||||
handleWebSocketOpen(ws as any, sessionId);
|
||||
@@ -110,7 +111,7 @@ app.get(
|
||||
handleWebSocketClose(ws as any, sessionId, closeEvt?.code, closeEvt?.reason);
|
||||
},
|
||||
onError(evt, ws) {
|
||||
console.error(`[WS] Error on session=${sessionId}:`, evt);
|
||||
logError(`[WS] Error on session=${sessionId}:`, evt);
|
||||
handleWebSocketClose(ws as any, sessionId, 1006, "websocket error");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { log, error as logError } from "../../logger";
|
||||
import { Hono } from "hono";
|
||||
import {
|
||||
createSession,
|
||||
@@ -23,7 +24,7 @@ app.post("/", acceptCliHeaders, apiKeyAuth, async (c) => {
|
||||
try {
|
||||
await createWorkItem(body.environment_id, session.id);
|
||||
} catch (err) {
|
||||
console.error(`[RCS] Failed to create work item: ${(err as Error).message}`);
|
||||
logError(`[RCS] Failed to create work item: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { log, error as logError } from "../../logger";
|
||||
import { Hono } from "hono";
|
||||
import { uuidAuth } from "../../auth/middleware";
|
||||
import { getSession, isSessionClosedStatus, resolveOwnedWebSessionId, updateSessionStatus } from "../../services/session";
|
||||
@@ -44,9 +45,9 @@ app.post("/sessions/:id/events", uuidAuth, async (c) => {
|
||||
|
||||
const body = await c.req.json();
|
||||
const eventType = body.type || "user";
|
||||
console.log(`[RC-DEBUG] web -> server: POST /web/sessions/${sessionId}/events type=${eventType} content=${JSON.stringify(body).slice(0, 200)}`);
|
||||
log(`[RC-DEBUG] web -> server: POST /web/sessions/${sessionId}/events type=${eventType} content=${JSON.stringify(body).slice(0, 200)}`);
|
||||
const event = publishSessionEvent(sessionId, eventType, body, "outbound");
|
||||
console.log(`[RC-DEBUG] web -> server: published outbound event id=${event.id} type=${event.type} direction=${event.direction} subscribers=${getEventBus(sessionId).subscriberCount()}`);
|
||||
log(`[RC-DEBUG] web -> server: published outbound event id=${event.id} type=${event.type} direction=${event.direction} subscribers=${getEventBus(sessionId).subscriberCount()}`);
|
||||
return c.json({ status: "ok", event }, 200);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { log, error as logError } from "../../logger";
|
||||
import { Hono } from "hono";
|
||||
import { uuidAuth } from "../../auth/middleware";
|
||||
import {
|
||||
@@ -35,7 +36,7 @@ app.post("/sessions", uuidAuth, async (c) => {
|
||||
try {
|
||||
await createWorkItem(body.environment_id, session.id);
|
||||
} catch (err) {
|
||||
console.error(`[RCS] Failed to create work item: ${(err as Error).message}`);
|
||||
logError(`[RCS] Failed to create work item: ${(err as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { log, error as logError } from "../logger";
|
||||
import { storeListActiveEnvironments, storeUpdateEnvironment } from "../store";
|
||||
import { storeListSessions } from "../store";
|
||||
import { config } from "../config";
|
||||
@@ -10,7 +11,7 @@ export function runDisconnectMonitorSweep(now = Date.now()) {
|
||||
const envs = storeListActiveEnvironments();
|
||||
for (const env of envs) {
|
||||
if (env.lastPollAt && now - env.lastPollAt.getTime() > timeoutMs) {
|
||||
console.log(`[RCS] Environment ${env.id} timed out (no poll for ${Math.round((now - env.lastPollAt.getTime()) / 1000)}s)`);
|
||||
log(`[RCS] Environment ${env.id} timed out (no poll for ${Math.round((now - env.lastPollAt.getTime()) / 1000)}s)`);
|
||||
storeUpdateEnvironment(env.id, { status: "disconnected" });
|
||||
}
|
||||
}
|
||||
@@ -21,7 +22,7 @@ export function runDisconnectMonitorSweep(now = Date.now()) {
|
||||
if (session.status === "running" || session.status === "idle") {
|
||||
const elapsed = now - session.updatedAt.getTime();
|
||||
if (elapsed > timeoutMs * 2) {
|
||||
console.log(`[RCS] Session ${session.id} marked inactive (no update for ${Math.round(elapsed / 1000)}s)`);
|
||||
log(`[RCS] Session ${session.id} marked inactive (no update for ${Math.round(elapsed / 1000)}s)`);
|
||||
updateSessionStatus(session.id, "inactive");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { log, error as logError } from "../logger";
|
||||
import {
|
||||
storeCreateWorkItem,
|
||||
storeGetWorkItem,
|
||||
@@ -35,7 +36,7 @@ export async function createWorkItem(environmentId: string, sessionId: string):
|
||||
|
||||
const secret = encodeWorkSecret();
|
||||
const record = storeCreateWorkItem({ environmentId, sessionId, secret });
|
||||
console.log(`[RCS] Work item created: ${record.id} for env=${environmentId} session=${sessionId}`);
|
||||
log(`[RCS] Work item created: ${record.id} for env=${environmentId} session=${sessionId}`);
|
||||
return record.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { log, error as logError } from "../logger";
|
||||
|
||||
export interface SessionEvent {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
@@ -33,12 +35,12 @@ export class EventBus {
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
this.events.push(full);
|
||||
console.log(`[RC-DEBUG] bus publish: sessionId=${event.sessionId} type=${event.type} dir=${event.direction} seq=${full.seqNum} subscribers=${this.subscribers.size}`);
|
||||
log(`[RC-DEBUG] bus publish: sessionId=${event.sessionId} type=${event.type} dir=${event.direction} seq=${full.seqNum} subscribers=${this.subscribers.size}`);
|
||||
for (const cb of this.subscribers) {
|
||||
try {
|
||||
cb(full);
|
||||
} catch (err) {
|
||||
console.error(`[RC-DEBUG] bus subscriber error:`, err);
|
||||
logError(`[RC-DEBUG] bus subscriber error:`, err);
|
||||
}
|
||||
}
|
||||
return full;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { log, error as logError } from "../logger";
|
||||
import type { Context } from "hono";
|
||||
import type { SessionEvent } from "./event-bus";
|
||||
import { getEventBus } from "./event-bus";
|
||||
@@ -76,7 +77,7 @@ export function createSSEStream(c: Context, sessionId: string, fromSeqNum = 0) {
|
||||
seqNum: event.seqNum,
|
||||
});
|
||||
try {
|
||||
console.log(`[RC-DEBUG] SSE -> web: sessionId=${sessionId} type=${event.type} dir=${event.direction} seq=${event.seqNum}`);
|
||||
log(`[RC-DEBUG] SSE -> web: sessionId=${sessionId} type=${event.type} dir=${event.direction} seq=${event.seqNum}`);
|
||||
controller.enqueue(encoder.encode(`id: ${event.seqNum}\nevent: message\ndata: ${data}\n\n`));
|
||||
} catch {
|
||||
unsub();
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { WSContext } from "hono/ws";
|
||||
import { getEventBus } from "./event-bus";
|
||||
import type { SessionEvent } from "./event-bus";
|
||||
import { publishSessionEvent } from "../services/transport";
|
||||
import { log, error as logError } from "../logger";
|
||||
|
||||
// Per-connection cleanup, keyed by sessionId (only one WS per session)
|
||||
interface CleanupEntry {
|
||||
@@ -97,13 +98,13 @@ function toSDKMessage(event: SessionEvent): string {
|
||||
/** Called from onOpen — subscribes to event bus, forwards outbound events to bridge WS */
|
||||
export function handleWebSocketOpen(ws: WSContext, sessionId: string) {
|
||||
const openTime = Date.now();
|
||||
console.log(`[RC-DEBUG] [WS] Open session=${sessionId}`);
|
||||
log(`[RC-DEBUG] [WS] Open session=${sessionId}`);
|
||||
activeConnections.add(ws);
|
||||
|
||||
// If there's an existing connection for this session, clean it up first
|
||||
const existing = cleanupBySession.get(sessionId);
|
||||
if (existing) {
|
||||
console.log(`[WS] Replacing existing connection for session=${sessionId}`);
|
||||
log(`[WS] Replacing existing connection for session=${sessionId}`);
|
||||
existing.unsub();
|
||||
clearInterval(existing.keepalive);
|
||||
activeConnections.delete(existing.ws);
|
||||
@@ -115,7 +116,7 @@ export function handleWebSocketOpen(ws: WSContext, sessionId: string) {
|
||||
// the full conversation history — assistant replies are inbound events.
|
||||
const missed = bus.getEventsSince(0);
|
||||
if (missed.length > 0) {
|
||||
console.log(`[WS] Replaying ${missed.length} missed event(s)`);
|
||||
log(`[WS] Replaying ${missed.length} missed event(s)`);
|
||||
for (const event of missed) {
|
||||
if (ws.readyState !== 1) break;
|
||||
try {
|
||||
@@ -131,10 +132,10 @@ export function handleWebSocketOpen(ws: WSContext, sessionId: string) {
|
||||
if (event.direction !== "outbound") return;
|
||||
try {
|
||||
const sdkMsg = toSDKMessage(event);
|
||||
console.log(`[RC-DEBUG] [WS] -> bridge (outbound): type=${event.type} len=${sdkMsg.length} msg=${sdkMsg.slice(0, 300)}`);
|
||||
log(`[RC-DEBUG] [WS] -> bridge (outbound): type=${event.type} len=${sdkMsg.length} msg=${sdkMsg.slice(0, 300)}`);
|
||||
ws.send(sdkMsg);
|
||||
} catch (err) {
|
||||
console.error("[RC-DEBUG] [WS] send error:", err);
|
||||
logError("[RC-DEBUG] [WS] send error:", err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -162,7 +163,7 @@ export function handleWebSocketMessage(ws: WSContext, sessionId: string, data: s
|
||||
try {
|
||||
ingestBridgeMessage(sessionId, JSON.parse(line));
|
||||
} catch (err) {
|
||||
console.error("[WS] parse error:", err);
|
||||
logError("[WS] parse error:", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +175,7 @@ export function handleWebSocketClose(ws: WSContext, sessionId: string, code?: nu
|
||||
const entry = cleanupBySession.get(sessionId);
|
||||
const duration = entry ? Math.round((Date.now() - entry.openTime) / 1000) : -1;
|
||||
|
||||
console.log(`[WS] Close session=${sessionId} code=${code ?? "none"} reason=${reason || "(none)"} duration=${duration}s`);
|
||||
log(`[WS] Close session=${sessionId} code=${code ?? "none"} reason=${reason || "(none)"} duration=${duration}s`);
|
||||
|
||||
if (entry) {
|
||||
entry.unsub();
|
||||
@@ -216,7 +217,7 @@ export function ingestBridgeMessage(sessionId: string, msg: Record<string, unkno
|
||||
|
||||
const eventType = deriveEventType(msg);
|
||||
|
||||
console.log(`[RC-DEBUG] [WS] <- bridge (inbound): sessionId=${sessionId} type=${eventType}${msg.uuid ? ` uuid=${msg.uuid}` : ""} msg=${JSON.stringify(msg).slice(0, 300)}`);
|
||||
log(`[RC-DEBUG] [WS] <- bridge (inbound): sessionId=${sessionId} type=${eventType}${msg.uuid ? ` uuid=${msg.uuid}` : ""} msg=${JSON.stringify(msg).slice(0, 300)}`);
|
||||
|
||||
let payload: unknown;
|
||||
|
||||
@@ -256,7 +257,7 @@ export function closeAllConnections(): void {
|
||||
const count = activeConnections.size;
|
||||
if (count === 0) return;
|
||||
|
||||
console.log(`[WS] Gracefully closing ${count} active connection(s)...`);
|
||||
log(`[WS] Gracefully closing ${count} active connection(s)...`);
|
||||
for (const [sessionId, entry] of cleanupBySession) {
|
||||
try {
|
||||
entry.unsub();
|
||||
@@ -270,5 +271,5 @@ export function closeAllConnections(): void {
|
||||
}
|
||||
cleanupBySession.clear();
|
||||
activeConnections.clear();
|
||||
console.log("[WS] All connections closed");
|
||||
log("[WS] All connections closed");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist", "web"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user