@dev-anywhere/proxy 0.1.7 → 0.1.8

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  ControlRequestEventSchema,
4
4
  SeqCounter
5
- } from "./chunk-ODK6N2NP.js";
5
+ } from "./chunk-PDX6QFJ7.js";
6
6
  import {
7
7
  CLAUDE_PROVIDER
8
8
  } from "./chunk-6O6JTF24.js";
@@ -10,7 +10,8 @@ import {
10
10
  LineBuffer,
11
11
  createWorkerReader,
12
12
  serializeWorkerMsg
13
- } from "./chunk-RFBTVZ2X.js";
13
+ } from "./chunk-BLWDLNT6.js";
14
+ import "./chunk-2XO3KLWW.js";
14
15
 
15
16
  // src/session-worker.ts
16
17
  import { createServer } from "net";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/session-worker.ts","../src/worker/json-session.ts"],"sourcesContent":["import { createServer, type Socket } from \"node:net\";\nimport { mkdirSync, unlinkSync, existsSync, chmodSync } from \"node:fs\";\nimport {\n JsonSession,\n ToolWhitelist,\n createRelayApprovalStrategy,\n type StreamJsonEvent,\n type ClaudePermissionMode,\n} from \"./worker/json-session.js\";\nimport { SeqCounter } from \"./common/seq-counter.js\";\nimport { createWorkerReader, serializeWorkerMsg, type WorkerMessage } from \"./ipc/ipc-protocol.js\";\nimport type { ProviderHookContext } from \"./providers/index.js\";\n\n// 参数格式: session-worker.ts <sessionId> <socketPath> [--cwd <dir>] [--resume <id>] [-- claude args...]\nconst sessionId = process.argv[2];\nconst sockPath = process.argv[3];\nconst separatorIdx = process.argv.indexOf(\"--\");\nconst claudeArgs = separatorIdx >= 0 ? process.argv.slice(separatorIdx + 1) : [];\n\n// 解析 -- 之前的可选参数\nconst preArgs = process.argv.slice(4, separatorIdx >= 0 ? separatorIdx : undefined);\nfunction getArg(name: string): string | undefined {\n const idx = preArgs.indexOf(name);\n return idx >= 0 && idx + 1 < preArgs.length ? preArgs[idx + 1] : undefined;\n}\nfunction hasFlag(name: string): boolean {\n return preArgs.includes(name);\n}\nconst workerCwd = getArg(\"--cwd\");\nconst workerResume = getArg(\"--resume\");\nconst workerPermissionMode = getArg(\"--permission-mode\") as ClaudePermissionMode | undefined;\nconst workerStreamDelta = hasFlag(\"--stream-delta\");\nconst workerHookUrl = getArg(\"--hook-url\");\nconst workerHookMarker = getArg(\"--hook-marker\");\nconst workerHookToken = process.env.DEV_ANYWHERE_HOOK_TOKEN;\nconst workerHookProvider = getArg(\"--hook-provider\") as ProviderHookContext[\"provider\"] | undefined;\n\nif (!sessionId || !sockPath) {\n console.error(\"Usage: session-worker <sessionId> <socketPath> [-- claudeArgs...]\");\n process.exit(1);\n}\n\nconst workerHook: ProviderHookContext | undefined =\n workerHookUrl && workerHookMarker && workerHookToken && workerHookProvider\n ? {\n provider: workerHookProvider,\n sessionId,\n hookUrl: workerHookUrl,\n marker: workerHookMarker,\n token: workerHookToken,\n }\n : undefined;\n\nlet serveSocket: Socket | null = null;\nconst seqCounter = new SeqCounter(sessionId);\nconst whitelist = new ToolWhitelist();\n\nconst pendingApprovals = new Map<\n string,\n {\n resolve: (decision: { behavior: \"allow\" | \"deny\"; message?: string }) => void;\n toolName: string;\n input: Record<string, unknown>;\n }\n>();\n\nfunction sendToServe(msg: WorkerMessage): void {\n if (serveSocket?.writable) {\n serveSocket.write(serializeWorkerMsg(msg));\n }\n}\n\n// 转发审批请求到 serve 进程,由 serve 进程通过 relay 转发到小程序\nconst forwardToRelay = async (\n toolName: string,\n input: Record<string, unknown>,\n): Promise<{ behavior: \"allow\" | \"deny\"; message?: string }> => {\n return new Promise((resolve) => {\n const requestId = `${sessionId}-${Date.now()}`;\n pendingApprovals.set(requestId, { resolve, toolName, input });\n sendToServe({\n type: \"worker_approval_request\",\n requestId,\n toolName,\n input,\n });\n });\n};\n\nconst session = new JsonSession({\n claudeArgs,\n cwd: workerCwd,\n resumeSessionId: workerResume,\n permissionMode: workerPermissionMode,\n includePartialMessages: workerStreamDelta,\n hook: workerHook,\n approvalStrategy: createRelayApprovalStrategy(whitelist, forwardToRelay),\n onEvent: (event: StreamJsonEvent) => {\n // 从 system 事件中捕获 Claude 会话 ID 并通知 serve\n if (event.type === \"system\" && typeof event.session_id === \"string\") {\n sendToServe({\n type: \"worker_claude_session_id\",\n sessionId: event.session_id,\n });\n }\n\n const seq = seqCounter.next();\n sendToServe({\n type: \"worker_event\",\n seq,\n event: event as Record<string, unknown>,\n });\n },\n onExit: (code: number) => {\n whitelist.clear();\n sendToServe({ type: \"worker_exit\", code });\n cleanup();\n process.exit(0);\n },\n});\n\nfunction handleServeConnection(socket: Socket): void {\n serveSocket = socket;\n\n for (const [requestId, pending] of pendingApprovals) {\n sendToServe({\n type: \"worker_approval_request\",\n requestId,\n toolName: pending.toolName,\n input: pending.input,\n });\n }\n\n createWorkerReader(socket, (msg: WorkerMessage) => {\n switch (msg.type) {\n case \"worker_input\":\n session.sendMessage(msg.content);\n break;\n case \"worker_stop\":\n session.stop();\n break;\n case \"worker_approval_response\": {\n const pending = pendingApprovals.get(msg.requestId);\n if (pending) {\n pending.resolve({ behavior: msg.behavior, message: msg.message });\n pendingApprovals.delete(msg.requestId);\n }\n break;\n }\n case \"worker_whitelist_add\":\n whitelist.add(msg.toolName);\n break;\n }\n });\n\n socket.on(\"close\", () => {\n serveSocket = null;\n });\n socket.on(\"error\", () => {\n serveSocket = null;\n });\n}\n\nconst sockDir = sockPath.substring(0, sockPath.lastIndexOf(\"/\"));\nmkdirSync(sockDir, { recursive: true });\n\nif (existsSync(sockPath)) {\n unlinkSync(sockPath);\n}\n\nconst server = createServer((socket) => {\n handleServeConnection(socket);\n});\n\nfunction cleanup(): void {\n server.close();\n try {\n unlinkSync(sockPath);\n } catch {\n // socket 文件可能已被删除\n }\n}\n\nprocess.on(\"SIGTERM\", () => {\n session.stop();\n});\n\nserver.listen(sockPath, () => {\n chmodSync(sockPath, 0o600);\n const pid = session.start();\n sendToServe({ type: \"worker_ready\", pid });\n});\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport type { z } from \"zod\";\nimport { LineBuffer } from \"../ipc/line-buffer.js\";\nimport { ControlRequestEventSchema } from \"../common/stream-json-schema.js\";\nimport {\n CLAUDE_PROVIDER,\n buildClaudeArgs,\n filterClaudeEnvVars,\n type ClaudePermissionMode,\n} from \"../providers/index.js\";\nimport type { ProviderHookContext } from \"../providers/index.js\";\n\nexport { buildClaudeArgs, filterClaudeEnvVars };\nexport type { ClaudePermissionMode };\n\n// stream-json event types observed from provider output.\nexport type StreamJsonEventType =\n | \"system\"\n | \"assistant\"\n | \"user\"\n | \"result\"\n | \"control_request\"\n | \"control_cancel_request\"\n | \"stream_event\";\n\nexport interface StreamJsonEvent {\n type: StreamJsonEventType;\n [key: string]: unknown;\n}\n\nexport type ApprovalStrategy = (\n toolName: string,\n input: Record<string, unknown>,\n) => Promise<{ behavior: \"allow\" | \"deny\"; message?: string }>;\n\ninterface JsonSessionOptions {\n workDir?: string;\n claudeArgs?: string[];\n approvalStrategy?: ApprovalStrategy;\n onEvent?: (event: StreamJsonEvent) => void;\n onExit?: (code: number) => void;\n cwd?: string;\n resumeSessionId?: string;\n permissionMode?: ClaudePermissionMode;\n includePartialMessages?: boolean;\n hook?: ProviderHookContext;\n}\n\n// 默认拒绝所有工具调用,远程审批未配置前的安全兜底\nconst denyAllStrategy: ApprovalStrategy = async () => ({\n behavior: \"deny\" as const,\n message: \"Tool use denied by default policy. Remote approval not yet configured.\",\n});\n\n// 会话级别的工具白名单,用户点击\"全部允许\"后同名工具自动审批\nexport class ToolWhitelist {\n private allowed = new Set<string>();\n\n has(toolName: string): boolean {\n return this.allowed.has(toolName);\n }\n\n add(toolName: string): void {\n this.allowed.add(toolName);\n }\n\n clear(): void {\n this.allowed.clear();\n }\n}\n\n// 创建中继转发审批策略,先检查白名单再转发到 relay\nexport function createRelayApprovalStrategy(\n whitelist: ToolWhitelist,\n forwardToRelay: (\n toolName: string,\n input: Record<string, unknown>,\n ) => Promise<{ behavior: \"allow\" | \"deny\"; message?: string }>,\n): ApprovalStrategy {\n return async (toolName, input) => {\n if (whitelist.has(toolName)) {\n return { behavior: \"allow\", message: \"Auto-approved by session whitelist\" };\n }\n return forwardToRelay(toolName, input);\n };\n}\n\nexport class JsonSession {\n private child: ChildProcess | null = null;\n private stderrChunks: string[] = [];\n private writeQueue: Promise<void> = Promise.resolve();\n private claudeSessionId: string | null = null;\n private readonly workDir: string;\n private readonly claudeArgs: string[];\n private readonly approvalStrategy: ApprovalStrategy;\n private readonly onEvent?: (event: StreamJsonEvent) => void;\n private readonly onExitCb?: (code: number) => void;\n private readonly resumeSessionId?: string;\n private readonly permissionMode?: ClaudePermissionMode;\n private readonly includePartialMessages: boolean;\n private readonly hook?: ProviderHookContext;\n\n constructor(options: JsonSessionOptions = {}) {\n this.workDir = options.cwd ?? options.workDir ?? process.cwd();\n this.claudeArgs = options.claudeArgs ?? [];\n this.approvalStrategy = options.approvalStrategy ?? denyAllStrategy;\n this.onEvent = options.onEvent;\n this.onExitCb = options.onExit;\n this.resumeSessionId = options.resumeSessionId;\n this.permissionMode = options.permissionMode;\n this.includePartialMessages = options.includePartialMessages ?? false;\n this.hook = options.hook;\n }\n\n getClaudeSessionId(): string | null {\n return this.claudeSessionId;\n }\n\n start(): number {\n const command = CLAUDE_PROVIDER.buildJsonCommand(\n {\n extraArgs: this.claudeArgs,\n permissionMode: this.permissionMode,\n resumeSessionId: this.resumeSessionId,\n includePartialMessages: this.includePartialMessages,\n hook: this.hook,\n },\n process.env,\n );\n\n this.child = spawn(command.command, command.args, {\n cwd: this.workDir,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: command.env,\n });\n\n this.setupStdoutParsing();\n this.setupStderrCollection();\n this.setupExitHandler();\n\n return this.child.pid!;\n }\n\n sendMessage(content: string): void {\n const message = {\n type: \"user\",\n message: { role: \"user\", content },\n };\n this.writeToStdin(JSON.stringify(message));\n }\n\n async stop(gracePeriodMs = 5000): Promise<void> {\n if (!this.child || !this.isAlive()) return;\n\n this.child.kill(\"SIGTERM\");\n\n const start = Date.now();\n while (Date.now() - start < gracePeriodMs) {\n if (!this.isAlive()) return;\n await new Promise((r) => setTimeout(r, 200));\n }\n\n if (this.isAlive()) {\n this.child.kill(\"SIGKILL\");\n }\n }\n\n isAlive(): boolean {\n if (!this.child || !this.child.pid) return false;\n try {\n process.kill(this.child.pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n getStderr(): string {\n return this.stderrChunks.join(\"\");\n }\n\n private setupStdoutParsing(): void {\n if (!this.child?.stdout) return;\n\n const lineBuffer = new LineBuffer();\n this.child.stdout.pipe(lineBuffer);\n\n lineBuffer.on(\"data\", (line: Buffer | string) => {\n const str = typeof line === \"string\" ? line : line.toString();\n let event: StreamJsonEvent;\n try {\n event = JSON.parse(str) as StreamJsonEvent;\n } catch {\n // 非 JSON 行直接跳过,verbose 模式会输出调试日志\n return;\n }\n\n // 从 system 事件中捕获 Claude 会话 ID 用于后续 resume\n if (event.type === \"system\" && typeof event.session_id === \"string\") {\n this.claudeSessionId = event.session_id;\n }\n\n if (event.type === \"control_request\") {\n // schema parse 失败说明 CLI 协议漂移,调用方必须感知而不是静默吃掉\n const parsed = ControlRequestEventSchema.safeParse(event);\n if (!parsed.success) {\n console.error(\n \"[json-session] control_request shape mismatch; skipping approval\",\n parsed.error.issues.slice(0, 3),\n );\n return;\n }\n this.handleControlRequest(parsed.data);\n return;\n }\n\n this.onEvent?.(event);\n });\n }\n\n private setupStderrCollection(): void {\n if (!this.child?.stderr) return;\n this.child.stderr.on(\"data\", (chunk: Buffer | string) => {\n this.stderrChunks.push(typeof chunk === \"string\" ? chunk : chunk.toString());\n });\n }\n\n private setupExitHandler(): void {\n if (!this.child) return;\n this.child.on(\"exit\", (code: number | null) => {\n this.onExitCb?.(code ?? 1);\n });\n }\n\n private handleControlRequest(event: z.infer<typeof ControlRequestEventSchema>): void {\n const requestId = event.request_id;\n const request = event.request;\n\n this.approvalStrategy(request.tool_name, request.input).then((decision) => {\n const response =\n decision.behavior === \"deny\"\n ? {\n type: \"control_response\",\n response: {\n subtype: \"success\",\n request_id: requestId,\n response: {\n behavior: \"deny\",\n message: decision.message ?? \"Tool use denied by default policy.\",\n },\n },\n }\n : {\n type: \"control_response\",\n response: {\n subtype: \"success\",\n request_id: requestId,\n response: {\n behavior: \"allow\",\n updatedInput: {},\n },\n },\n };\n\n this.writeToStdin(JSON.stringify(response));\n });\n }\n\n private writeToStdin(data: string): void {\n this.writeQueue = this.writeQueue.then(() => {\n return new Promise<void>((resolve, reject) => {\n if (!this.child?.stdin?.writable) {\n reject(new Error(\"stdin not writable\"));\n return;\n }\n this.child.stdin.write(data + \"\\n\", (err) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n });\n }\n}\n"],"mappings":"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","names":["whitelist","forwardToRelay"]}
1
+ {"version":3,"sources":["../src/session-worker.ts","../src/worker/json-session.ts"],"sourcesContent":["import { createServer, type Socket } from \"node:net\";\nimport { mkdirSync, unlinkSync, existsSync, chmodSync } from \"node:fs\";\nimport {\n JsonSession,\n ToolWhitelist,\n createRelayApprovalStrategy,\n type StreamJsonEvent,\n type ClaudePermissionMode,\n} from \"./worker/json-session.js\";\nimport { SeqCounter } from \"./common/seq-counter.js\";\nimport { createWorkerReader, serializeWorkerMsg, type WorkerMessage } from \"./ipc/ipc-protocol.js\";\nimport type { ProviderHookContext } from \"./providers/index.js\";\n\n// 参数格式: session-worker.ts <sessionId> <socketPath> [--cwd <dir>] [--resume <id>] [-- claude args...]\nconst sessionId = process.argv[2];\nconst sockPath = process.argv[3];\nconst separatorIdx = process.argv.indexOf(\"--\");\nconst claudeArgs = separatorIdx >= 0 ? process.argv.slice(separatorIdx + 1) : [];\n\n// 解析 -- 之前的可选参数\nconst preArgs = process.argv.slice(4, separatorIdx >= 0 ? separatorIdx : undefined);\nfunction getArg(name: string): string | undefined {\n const idx = preArgs.indexOf(name);\n return idx >= 0 && idx + 1 < preArgs.length ? preArgs[idx + 1] : undefined;\n}\nfunction hasFlag(name: string): boolean {\n return preArgs.includes(name);\n}\nconst workerCwd = getArg(\"--cwd\");\nconst workerResume = getArg(\"--resume\");\nconst workerPermissionMode = getArg(\"--permission-mode\") as ClaudePermissionMode | undefined;\nconst workerStreamDelta = hasFlag(\"--stream-delta\");\nconst workerHookUrl = getArg(\"--hook-url\");\nconst workerHookMarker = getArg(\"--hook-marker\");\nconst workerHookToken = process.env.DEV_ANYWHERE_HOOK_TOKEN;\nconst workerHookProvider = getArg(\"--hook-provider\") as ProviderHookContext[\"provider\"] | undefined;\n\nif (!sessionId || !sockPath) {\n console.error(\"Usage: session-worker <sessionId> <socketPath> [-- claudeArgs...]\");\n process.exit(1);\n}\n\nconst workerHook: ProviderHookContext | undefined =\n workerHookUrl && workerHookMarker && workerHookToken && workerHookProvider\n ? {\n provider: workerHookProvider,\n sessionId,\n hookUrl: workerHookUrl,\n marker: workerHookMarker,\n token: workerHookToken,\n }\n : undefined;\n\nlet serveSocket: Socket | null = null;\nconst seqCounter = new SeqCounter(sessionId);\nconst whitelist = new ToolWhitelist();\n\nconst pendingApprovals = new Map<\n string,\n {\n resolve: (decision: { behavior: \"allow\" | \"deny\"; message?: string }) => void;\n toolName: string;\n input: Record<string, unknown>;\n }\n>();\n\nfunction sendToServe(msg: WorkerMessage): void {\n if (serveSocket?.writable) {\n serveSocket.write(serializeWorkerMsg(msg));\n }\n}\n\n// 转发审批请求到 serve 进程,由 serve 进程通过 relay 转发到小程序\nconst forwardToRelay = async (\n toolName: string,\n input: Record<string, unknown>,\n): Promise<{ behavior: \"allow\" | \"deny\"; message?: string }> => {\n return new Promise((resolve) => {\n const requestId = `${sessionId}-${Date.now()}`;\n pendingApprovals.set(requestId, { resolve, toolName, input });\n sendToServe({\n type: \"worker_approval_request\",\n requestId,\n toolName,\n input,\n });\n });\n};\n\nconst session = new JsonSession({\n claudeArgs,\n cwd: workerCwd,\n resumeSessionId: workerResume,\n permissionMode: workerPermissionMode,\n includePartialMessages: workerStreamDelta,\n hook: workerHook,\n approvalStrategy: createRelayApprovalStrategy(whitelist, forwardToRelay),\n onEvent: (event: StreamJsonEvent) => {\n // 从 system 事件中捕获 Claude 会话 ID 并通知 serve\n if (event.type === \"system\" && typeof event.session_id === \"string\") {\n sendToServe({\n type: \"worker_claude_session_id\",\n sessionId: event.session_id,\n });\n }\n\n const seq = seqCounter.next();\n sendToServe({\n type: \"worker_event\",\n seq,\n event: event as Record<string, unknown>,\n });\n },\n onExit: (code: number) => {\n whitelist.clear();\n sendToServe({ type: \"worker_exit\", code });\n cleanup();\n process.exit(0);\n },\n});\n\nfunction handleServeConnection(socket: Socket): void {\n serveSocket = socket;\n\n for (const [requestId, pending] of pendingApprovals) {\n sendToServe({\n type: \"worker_approval_request\",\n requestId,\n toolName: pending.toolName,\n input: pending.input,\n });\n }\n\n createWorkerReader(socket, (msg: WorkerMessage) => {\n switch (msg.type) {\n case \"worker_input\":\n session.sendMessage(msg.content);\n break;\n case \"worker_stop\":\n session.stop();\n break;\n case \"worker_approval_response\": {\n const pending = pendingApprovals.get(msg.requestId);\n if (pending) {\n pending.resolve({ behavior: msg.behavior, message: msg.message });\n pendingApprovals.delete(msg.requestId);\n }\n break;\n }\n case \"worker_whitelist_add\":\n whitelist.add(msg.toolName);\n break;\n }\n });\n\n socket.on(\"close\", () => {\n serveSocket = null;\n });\n socket.on(\"error\", () => {\n serveSocket = null;\n });\n}\n\nconst sockDir = sockPath.substring(0, sockPath.lastIndexOf(\"/\"));\nmkdirSync(sockDir, { recursive: true });\n\nif (existsSync(sockPath)) {\n unlinkSync(sockPath);\n}\n\nconst server = createServer((socket) => {\n handleServeConnection(socket);\n});\n\nfunction cleanup(): void {\n server.close();\n try {\n unlinkSync(sockPath);\n } catch {\n // socket 文件可能已被删除\n }\n}\n\nprocess.on(\"SIGTERM\", () => {\n session.stop();\n});\n\nserver.listen(sockPath, () => {\n chmodSync(sockPath, 0o600);\n const pid = session.start();\n sendToServe({ type: \"worker_ready\", pid });\n});\n","import { spawn, type ChildProcess } from \"node:child_process\";\nimport type { z } from \"zod\";\nimport { LineBuffer } from \"../ipc/line-buffer.js\";\nimport { ControlRequestEventSchema } from \"../common/stream-json-schema.js\";\nimport {\n CLAUDE_PROVIDER,\n buildClaudeArgs,\n filterClaudeEnvVars,\n type ClaudePermissionMode,\n} from \"../providers/index.js\";\nimport type { ProviderHookContext } from \"../providers/index.js\";\n\nexport { buildClaudeArgs, filterClaudeEnvVars };\nexport type { ClaudePermissionMode };\n\n// stream-json event types observed from provider output.\nexport type StreamJsonEventType =\n | \"system\"\n | \"assistant\"\n | \"user\"\n | \"result\"\n | \"control_request\"\n | \"control_cancel_request\"\n | \"stream_event\";\n\nexport interface StreamJsonEvent {\n type: StreamJsonEventType;\n [key: string]: unknown;\n}\n\nexport type ApprovalStrategy = (\n toolName: string,\n input: Record<string, unknown>,\n) => Promise<{ behavior: \"allow\" | \"deny\"; message?: string }>;\n\ninterface JsonSessionOptions {\n workDir?: string;\n claudeArgs?: string[];\n approvalStrategy?: ApprovalStrategy;\n onEvent?: (event: StreamJsonEvent) => void;\n onExit?: (code: number) => void;\n cwd?: string;\n resumeSessionId?: string;\n permissionMode?: ClaudePermissionMode;\n includePartialMessages?: boolean;\n hook?: ProviderHookContext;\n}\n\n// 默认拒绝所有工具调用,远程审批未配置前的安全兜底\nconst denyAllStrategy: ApprovalStrategy = async () => ({\n behavior: \"deny\" as const,\n message: \"Tool use denied by default policy. Remote approval not yet configured.\",\n});\n\n// 会话级别的工具白名单,用户点击\"全部允许\"后同名工具自动审批\nexport class ToolWhitelist {\n private allowed = new Set<string>();\n\n has(toolName: string): boolean {\n return this.allowed.has(toolName);\n }\n\n add(toolName: string): void {\n this.allowed.add(toolName);\n }\n\n clear(): void {\n this.allowed.clear();\n }\n}\n\n// 创建中继转发审批策略,先检查白名单再转发到 relay\nexport function createRelayApprovalStrategy(\n whitelist: ToolWhitelist,\n forwardToRelay: (\n toolName: string,\n input: Record<string, unknown>,\n ) => Promise<{ behavior: \"allow\" | \"deny\"; message?: string }>,\n): ApprovalStrategy {\n return async (toolName, input) => {\n if (whitelist.has(toolName)) {\n return { behavior: \"allow\", message: \"Auto-approved by session whitelist\" };\n }\n return forwardToRelay(toolName, input);\n };\n}\n\nexport class JsonSession {\n private child: ChildProcess | null = null;\n private stderrChunks: string[] = [];\n private writeQueue: Promise<void> = Promise.resolve();\n private claudeSessionId: string | null = null;\n private readonly workDir: string;\n private readonly claudeArgs: string[];\n private readonly approvalStrategy: ApprovalStrategy;\n private readonly onEvent?: (event: StreamJsonEvent) => void;\n private readonly onExitCb?: (code: number) => void;\n private readonly resumeSessionId?: string;\n private readonly permissionMode?: ClaudePermissionMode;\n private readonly includePartialMessages: boolean;\n private readonly hook?: ProviderHookContext;\n\n constructor(options: JsonSessionOptions = {}) {\n this.workDir = options.cwd ?? options.workDir ?? process.cwd();\n this.claudeArgs = options.claudeArgs ?? [];\n this.approvalStrategy = options.approvalStrategy ?? denyAllStrategy;\n this.onEvent = options.onEvent;\n this.onExitCb = options.onExit;\n this.resumeSessionId = options.resumeSessionId;\n this.permissionMode = options.permissionMode;\n this.includePartialMessages = options.includePartialMessages ?? false;\n this.hook = options.hook;\n }\n\n getClaudeSessionId(): string | null {\n return this.claudeSessionId;\n }\n\n start(): number {\n const command = CLAUDE_PROVIDER.buildJsonCommand(\n {\n extraArgs: this.claudeArgs,\n permissionMode: this.permissionMode,\n resumeSessionId: this.resumeSessionId,\n includePartialMessages: this.includePartialMessages,\n hook: this.hook,\n },\n process.env,\n );\n\n this.child = spawn(command.command, command.args, {\n cwd: this.workDir,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: command.env,\n });\n\n this.setupStdoutParsing();\n this.setupStderrCollection();\n this.setupExitHandler();\n\n return this.child.pid!;\n }\n\n sendMessage(content: string): void {\n const message = {\n type: \"user\",\n message: { role: \"user\", content },\n };\n this.writeToStdin(JSON.stringify(message));\n }\n\n async stop(gracePeriodMs = 5000): Promise<void> {\n if (!this.child || !this.isAlive()) return;\n\n this.child.kill(\"SIGTERM\");\n\n const start = Date.now();\n while (Date.now() - start < gracePeriodMs) {\n if (!this.isAlive()) return;\n await new Promise((r) => setTimeout(r, 200));\n }\n\n if (this.isAlive()) {\n this.child.kill(\"SIGKILL\");\n }\n }\n\n isAlive(): boolean {\n if (!this.child || !this.child.pid) return false;\n try {\n process.kill(this.child.pid, 0);\n return true;\n } catch {\n return false;\n }\n }\n\n getStderr(): string {\n return this.stderrChunks.join(\"\");\n }\n\n private setupStdoutParsing(): void {\n if (!this.child?.stdout) return;\n\n const lineBuffer = new LineBuffer();\n this.child.stdout.pipe(lineBuffer);\n\n lineBuffer.on(\"data\", (line: Buffer | string) => {\n const str = typeof line === \"string\" ? line : line.toString();\n let event: StreamJsonEvent;\n try {\n event = JSON.parse(str) as StreamJsonEvent;\n } catch {\n // 非 JSON 行直接跳过,verbose 模式会输出调试日志\n return;\n }\n\n // 从 system 事件中捕获 Claude 会话 ID 用于后续 resume\n if (event.type === \"system\" && typeof event.session_id === \"string\") {\n this.claudeSessionId = event.session_id;\n }\n\n if (event.type === \"control_request\") {\n // schema parse 失败说明 CLI 协议漂移,调用方必须感知而不是静默吃掉\n const parsed = ControlRequestEventSchema.safeParse(event);\n if (!parsed.success) {\n console.error(\n \"[json-session] control_request shape mismatch; skipping approval\",\n parsed.error.issues.slice(0, 3),\n );\n return;\n }\n this.handleControlRequest(parsed.data);\n return;\n }\n\n this.onEvent?.(event);\n });\n }\n\n private setupStderrCollection(): void {\n if (!this.child?.stderr) return;\n this.child.stderr.on(\"data\", (chunk: Buffer | string) => {\n this.stderrChunks.push(typeof chunk === \"string\" ? chunk : chunk.toString());\n });\n }\n\n private setupExitHandler(): void {\n if (!this.child) return;\n this.child.on(\"exit\", (code: number | null) => {\n this.onExitCb?.(code ?? 1);\n });\n }\n\n private handleControlRequest(event: z.infer<typeof ControlRequestEventSchema>): void {\n const requestId = event.request_id;\n const request = event.request;\n\n this.approvalStrategy(request.tool_name, request.input).then((decision) => {\n const response =\n decision.behavior === \"deny\"\n ? {\n type: \"control_response\",\n response: {\n subtype: \"success\",\n request_id: requestId,\n response: {\n behavior: \"deny\",\n message: decision.message ?? \"Tool use denied by default policy.\",\n },\n },\n }\n : {\n type: \"control_response\",\n response: {\n subtype: \"success\",\n request_id: requestId,\n response: {\n behavior: \"allow\",\n updatedInput: {},\n },\n },\n };\n\n this.writeToStdin(JSON.stringify(response));\n });\n }\n\n private writeToStdin(data: string): void {\n this.writeQueue = this.writeQueue.then(() => {\n return new Promise<void>((resolve, reject) => {\n if (!this.child?.stdin?.writable) {\n reject(new Error(\"stdin not writable\"));\n return;\n }\n this.child.stdin.write(data + \"\\n\", (err) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n });\n }\n}\n"],"mappings":"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","names":["whitelist","forwardToRelay"]}
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  daemonRelayArgs
4
- } from "./chunk-TX6HNHDB.js";
4
+ } from "./chunk-2SBGSJLQ.js";
5
5
  import {
6
6
  createFSM,
7
7
  extractOscSequences,
8
8
  extractOscSignals,
9
9
  shouldReleaseApprovalWait,
10
- stateAfterApprovalRelease,
11
- terminalLogger
12
- } from "./chunk-JGGDVMY5.js";
10
+ stateAfterApprovalRelease
11
+ } from "./chunk-ORZTFYXR.js";
13
12
  import {
14
13
  spawnScript
15
14
  } from "./chunk-ZUWAB67J.js";
@@ -17,16 +16,21 @@ import {
17
16
  CLAUDE_PROVIDER,
18
17
  CODEX_PROVIDER
19
18
  } from "./chunk-6O6JTF24.js";
19
+ import {
20
+ createIpcReader,
21
+ encodeBinaryIpcFrame,
22
+ serializeIpc
23
+ } from "./chunk-BLWDLNT6.js";
24
+ import {
25
+ terminalLogger
26
+ } from "./chunk-GTTLWHIG.js";
20
27
  import {
21
28
  PROFILE_NAME,
22
29
  SERVICE_LOG_PATH,
23
30
  SOCK_PATH,
24
31
  STOPPED_PATH,
25
- createIpcReader,
26
- encodeBinaryIpcFrame,
27
- serializeIpc,
28
32
  tildify
29
- } from "./chunk-RFBTVZ2X.js";
33
+ } from "./chunk-2XO3KLWW.js";
30
34
 
31
35
  // src/terminal.ts
32
36
  import { connect } from "net";
@@ -676,4 +680,4 @@ async function startTerminal(providerArgs, providerId = providerFromEnv()) {
676
680
  export {
677
681
  startTerminal
678
682
  };
679
- //# sourceMappingURL=terminal-ES6I5W32.js.map
683
+ //# sourceMappingURL=terminal-4MDRBCL4.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/terminal.ts","../src/terminal/tty.ts","../src/terminal/pty-manager.ts","../src/terminal/cwd.ts","../src/terminal/state.ts"],"sourcesContent":["import { connect, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { readTtySize, notifyUser } from \"./terminal/tty.js\";\nimport { PtyManager } from \"./terminal/pty-manager.js\";\nimport { resolveTerminalCwd } from \"./terminal/cwd.js\";\nimport pkg from \"@xterm/headless\";\nconst { Terminal: HeadlessTerminal } = pkg;\nimport { SerializeAddon } from \"@xterm/addon-serialize\";\nimport { UnicodeGraphemesAddon } from \"@xterm/addon-unicode-graphemes\";\nimport {\n extractOscSequences,\n extractOscSignals,\n type PtySemanticState,\n} from \"./common/osc-extractor.js\";\nimport {\n shouldReleaseApprovalWait,\n stateAfterApprovalRelease,\n} from \"./common/pty-approval-state.js\";\nimport { TerminalState, TERMINAL_TRANSITIONS, createExitHandler } from \"./terminal/state.js\";\nimport {\n SOCK_PATH,\n STOPPED_PATH,\n SERVICE_LOG_PATH,\n PROFILE_NAME,\n tildify,\n} from \"./common/paths.js\";\nimport { spawnScript } from \"./common/env.js\";\nimport { daemonRelayArgs } from \"./common/daemon-env.js\";\nimport {\n createIpcReader,\n serializeIpc,\n encodeBinaryIpcFrame,\n type IpcMessage,\n} from \"./ipc/ipc-protocol.js\";\nimport { terminalLogger as log } from \"./common/logger.js\";\nimport { createFSM } from \"./common/state-machine.js\";\nimport {\n CLAUDE_PROVIDER,\n CODEX_PROVIDER,\n type ProviderAdapter,\n type ProviderHookContext,\n type ProviderId,\n} from \"./providers/index.js\";\n\n// serve daemon 自动拉起的连接重试参数\nconst ENSURE_SERVICE_MAX_RETRIES = 20;\nconst ENSURE_SERVICE_INITIAL_DELAY_MS = 100;\nconst ENSURE_SERVICE_MAX_DELAY_MS = 2_000;\n\n// 等待特定类型 IPC 消息的默认超时\nconst WAIT_FOR_MESSAGE_TIMEOUT_MS = 10_000;\n\n// idle 检测:超过 IDLE_THRESHOLD_MS 无输出则翻转 working -> turn_complete\nconst IDLE_CHECK_INTERVAL_MS = 3_000;\nconst IDLE_THRESHOLD_MS = 3_000;\n\n// serve 连接断开后的重连重试参数\nconst RECONNECT_INITIAL_DELAY_MS = 1_000;\nconst RECONNECT_MAX_DELAY_MS = 5_000;\n// 连续 spawn 失败到达阈值后停止自动 spawn,降为被动 tryConnect 轮询。\n// 作用:环境异常(端口占用、依赖缺失、权限不足)时避免反复拉起短命子进程把日志刷爆。\nconst SPAWN_FAILURE_THRESHOLD = 3;\n\nconst PROVIDERS: Record<ProviderId, ProviderAdapter> = {\n claude: CLAUDE_PROVIDER,\n codex: CODEX_PROVIDER,\n};\n\nfunction tryConnect(sockPath: string): Promise<Socket | null> {\n return new Promise((resolve) => {\n const s = connect(sockPath);\n s.on(\"connect\", () => resolve(s));\n s.on(\"error\", () => resolve(null));\n });\n}\n\nasync function ensureService(autoStart = true): Promise<Socket> {\n const existing = await tryConnect(SOCK_PATH);\n if (existing) {\n log.info(\"Connected to existing service\");\n return existing;\n }\n\n if (!autoStart) throw new Error(\"Service is not running\");\n\n if (existsSync(STOPPED_PATH)) unlinkSync(STOPPED_PATH);\n\n log.info(\"Auto-starting serve daemon\");\n const child = spawnScript(\n new URL(\"./serve\", import.meta.url),\n [\"--profile\", PROFILE_NAME, ...daemonRelayArgs()],\n {\n env: { ...process.env },\n logger: log,\n },\n );\n\n // 监听 daemon 失败信号,让下面的 tryConnect 轮询能在 daemon 启动时就崩的场景下立刻抛诊断。\n // - 'exit':进程启动成功后又退出(配置错误、端口占用、内部崩溃),带 code/signal。\n // - 'error':spawn 本身失败(ENOENT 找不到 tsx/node 等),Node 文档说此时 'exit' may or may not 跟着 fire,\n // 所以显式监听补完备性。spawnScript 内部另装了一对只管日志的监听器,跟这里互不影响。\n let childFailed = false;\n let exitCode: number | null = null;\n let exitSignal: NodeJS.Signals | null = null;\n let spawnError: Error | null = null;\n child.once(\"exit\", (code, signal) => {\n childFailed = true;\n exitCode = code;\n exitSignal = signal;\n });\n child.once(\"error\", (err) => {\n childFailed = true;\n spawnError = err;\n });\n\n for (let i = 0; i < ENSURE_SERVICE_MAX_RETRIES; i++) {\n const delay = Math.min(ENSURE_SERVICE_INITIAL_DELAY_MS * (i + 1), ENSURE_SERVICE_MAX_DELAY_MS);\n await sleep(delay);\n\n if (childFailed) {\n log.error(\n { code: exitCode, signal: exitSignal, err: spawnError && String(spawnError) },\n \"Serve daemon failed to start\",\n );\n const detail = spawnError\n ? `spawn error=${String(spawnError)}`\n : `code=${exitCode}, signal=${exitSignal}`;\n throw new Error(\n `Serve daemon failed to start (${detail}). Check ${SERVICE_LOG_PATH} for details.`,\n );\n }\n\n const socket = await tryConnect(SOCK_PATH);\n if (socket) {\n log.info({ attempt: i + 1 }, \"Connected to service after retry\");\n return socket;\n }\n }\n\n log.error({ maxRetries: ENSURE_SERVICE_MAX_RETRIES }, \"Failed to connect to service\");\n throw new Error(\n `Failed to connect to dev-anywhere service after ${ENSURE_SERVICE_MAX_RETRIES} retries. Check ${SERVICE_LOG_PATH} for details.`,\n );\n}\n\nfunction waitForMessage<T extends IpcMessage[\"type\"]>(\n socket: Socket,\n messageType: T,\n): Promise<Extract<IpcMessage, { type: T }>> {\n return new Promise((resolve, reject) => {\n let timeout: NodeJS.Timeout | null = null;\n const dispose = createIpcReader(socket, (msg: IpcMessage) => {\n if (msg.type === messageType) {\n if (timeout) clearTimeout(timeout);\n dispose();\n resolve(msg as Extract<IpcMessage, { type: T }>);\n }\n });\n timeout = setTimeout(() => {\n dispose();\n reject(new Error(`Timeout waiting for ${messageType}`));\n }, WAIT_FOR_MESSAGE_TIMEOUT_MS);\n });\n}\n\nclass TerminalSession {\n private readonly fsm = createFSM<TerminalState>({\n initial: TerminalState.INIT,\n transitions: TERMINAL_TRANSITIONS,\n onTransition: (from, to) => log.info({ from, to }, \"Terminal state transition\"),\n });\n private readonly sessionCwd = resolveTerminalCwd();\n // socket 在 run() 中连上 serve 后首次赋值;reconnect 会重新赋值为新实例\n private socket!: Socket;\n private sessionId: string | null = null;\n private hookContext: ProviderHookContext | null = null;\n private ptyManager: PtyManager | null = null;\n private lastOutputTime = 0;\n private idleCheckTimer: NodeJS.Timeout | null = null;\n private currentPtyState: PtySemanticState = \"turn_complete\";\n // headless terminal 在本进程维护,用于按需 serialize() 给远程 client\n private headlessTerminal: InstanceType<typeof HeadlessTerminal> | null = null;\n private serializeAddon: SerializeAddon | null = null;\n private outputSeq = 0;\n private remoteDetached = false;\n // 记录上次 bridge 状态避免重连抖动导致 banner 连刷;初值 null 让首次状态(无论真假)都打,启动时提示 remote viewing 是否就绪\n private lastBridgeConnected: boolean | null = null;\n // 收尾函数在 run() 里创建一次,PTY 退出与 SIGTERM 共用;内部通过 fsm EXITED 检查短路\n private cleanupAndExit!: (code: number) => void;\n\n constructor(\n private readonly provider: ProviderAdapter,\n private readonly providerArgs: string[],\n ) {}\n\n async run(): Promise<void> {\n log.info(\"Terminal starting\");\n this.fsm.transitionTo(TerminalState.CONNECTING_SERVICE);\n this.socket = await ensureService();\n\n await this.createSession();\n this.initHeadlessTerminal();\n this.cleanupAndExit = createExitHandler({\n fsm: this.fsm,\n getSocket: () => this.socket,\n getSessionId: () => this.sessionId,\n getIdleCheckTimer: () => this.idleCheckTimer,\n });\n\n this.setupSocketHandlers();\n this.startPtyManager();\n\n this.socket.write(\n serializeIpc({ type: \"pty_register\", sessionId: this.sessionId!, pid: process.pid }),\n );\n this.replayCurrentPtyState();\n this.fsm.transitionTo(TerminalState.RUNNING);\n this.setupIdleCheck();\n\n process.on(\"SIGTERM\", () => {\n log.info({ sessionId: this.sessionId }, \"SIGTERM received, shutting down\");\n this.cleanupAndExit(143);\n });\n }\n\n private async createSession(): Promise<void> {\n this.fsm.transitionTo(TerminalState.CREATING_SESSION);\n const responsePromise = waitForMessage(this.socket, \"session_create_response\");\n this.socket.write(\n serializeIpc({\n type: \"session_create_request\",\n mode: \"pty\",\n provider: this.provider.id,\n cwd: this.sessionCwd,\n name: tildify(this.sessionCwd),\n pid: process.pid,\n }),\n );\n const response = await responsePromise;\n if (response.error) {\n throw new Error(`Failed to create session: ${response.error}`);\n }\n this.sessionId = response.sessionId;\n this.hookContext = response.hook ?? null;\n }\n\n private initHeadlessTerminal(): void {\n const { cols, rows } = readTtySize(process.stdout);\n log.info(\n { sessionId: this.sessionId, cols, rows },\n \"Session created, initializing headless terminal\",\n );\n this.headlessTerminal = new HeadlessTerminal({\n cols,\n rows,\n scrollback: 5000,\n allowProposedApi: true,\n });\n this.serializeAddon = new SerializeAddon();\n // UnicodeGraphemesAddon activate() 里会设置 activeVersion = '15-graphemes'\n this.headlessTerminal.loadAddon(this.serializeAddon);\n this.headlessTerminal.loadAddon(new UnicodeGraphemesAddon());\n }\n\n private startPtyManager(): void {\n this.ptyManager = new PtyManager({\n provider: this.provider,\n providerArgs: this.providerArgs,\n cwd: this.sessionCwd,\n hook: this.hookContext ?? undefined,\n tap: (data) => this.handlePtyData(data),\n stdin: process.stdin,\n stdout: process.stdout,\n onResize: (newCols, newRows) => {\n if (this.headlessTerminal) this.headlessTerminal.resize(newCols, newRows);\n if (this.socket.writable && this.sessionId) {\n this.socket.write(\n serializeIpc({\n type: \"pty_resize\",\n sessionId: this.sessionId,\n cols: newCols,\n rows: newRows,\n }),\n );\n }\n },\n onSessionExit: (code: number) => {\n log.info({ sessionId: this.sessionId, exitCode: code }, \"PTY exited, cleaning up\");\n this.cleanupAndExit(code);\n },\n });\n this.ptyManager.start();\n log.info({ sessionId: this.sessionId }, \"PTY started with headless terminal\");\n }\n\n // PTY 的每一帧输出都要:追到 headless terminal 状态、推 binary IPC、提取 provider 语义事件\n private handlePtyData(data: string): void {\n this.lastOutputTime = Date.now();\n this.outputSeq += 1;\n\n if (this.headlessTerminal) this.headlessTerminal.write(data);\n\n if (!this.remoteDetached && this.socket.writable && this.sessionId) {\n this.socket.write(\n encodeBinaryIpcFrame(this.sessionId, Buffer.from(data, \"utf-8\"), this.outputSeq),\n );\n }\n\n const oscSequences = extractOscSequences(data);\n const signal = extractOscSignals(data, this.provider.id);\n if (oscSequences.length > 0) {\n log.debug(\n {\n sessionId: this.sessionId,\n oscSequences,\n signal,\n },\n \"PTY OSC sequences parsed\",\n );\n }\n if (signal?.title) {\n this.sendTerminalTitle(signal.title);\n }\n if (signal?.state === \"approval_wait\") {\n this.currentPtyState = \"approval_wait\";\n this.sendPtyState(\"approval_wait\", { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (\n shouldReleaseApprovalWait({\n currentState: this.currentPtyState,\n signalState: signal?.state,\n })\n ) {\n const nextState = stateAfterApprovalRelease(signal?.state);\n this.currentPtyState = nextState;\n this.sendPtyState(nextState, { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (this.currentPtyState === \"approval_wait\" && signal?.state !== \"turn_complete\") {\n this.sendPtyState(\"approval_wait\", { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (signal && signal.state !== \"working\") {\n this.currentPtyState = signal.state;\n this.sendPtyState(signal.state, { title: signal.title, tool: signal.tool });\n return;\n }\n if (this.currentPtyState !== \"working\") {\n this.currentPtyState = \"working\";\n this.sendPtyState(\"working\");\n }\n }\n\n private sendTerminalTitle(title: string): void {\n if (this.remoteDetached || !this.socket.writable || !this.sessionId) return;\n this.socket.write(\n serializeIpc({\n type: \"pty_title_change\",\n sessionId: this.sessionId,\n title,\n }),\n );\n }\n\n private sendPtyState(state: PtySemanticState, meta?: { title?: string; tool?: string }): void {\n if (this.remoteDetached || !this.socket.writable || !this.sessionId) return;\n this.socket.write(\n serializeIpc({\n type: \"pty_semantic_event\",\n sessionId: this.sessionId,\n state,\n ...(meta?.title !== undefined ? { title: meta.title } : {}),\n ...(meta?.tool !== undefined ? { tool: meta.tool } : {}),\n }),\n );\n log.info(\n { sessionId: this.sessionId, state, title: meta?.title, tool: meta?.tool },\n \"PTY semantic event pushed\",\n );\n }\n\n private replayCurrentPtyState(): void {\n if (this.currentPtyState === \"turn_complete\") return;\n this.sendPtyState(this.currentPtyState);\n }\n\n private handleBridgeStatus(connected: boolean): void {\n if (this.remoteDetached) return;\n if (this.lastBridgeConnected === connected) return;\n this.lastBridgeConnected = connected;\n log.info({ connected }, \"Bridge status changed, notifying user\");\n notifyUser(connected ? \"relay online\" : \"relay offline — remote viewing unavailable\");\n }\n\n private setupSocketHandlers(): void {\n createIpcReader(this.socket, (msg: IpcMessage) => {\n if (msg.type === \"pty_input\" && msg.sessionId === this.sessionId) {\n log.debug({ sessionId: this.sessionId, bytes: msg.data.length }, \"Remote input received\");\n this.ptyManager?.write(msg.data);\n } else if (msg.type === \"pty_detach\" && msg.sessionId === this.sessionId) {\n this.detachRemoteView();\n } else if (msg.type === \"bridge_status\") {\n this.handleBridgeStatus(msg.connected);\n } else if (msg.type === \"pty_subscribe\" && msg.sessionId === this.sessionId) {\n if (this.serializeAddon && this.headlessTerminal) {\n const data = this.serializeAddon.serialize();\n this.socket.write(\n serializeIpc({\n type: \"pty_snapshot\",\n sessionId: msg.sessionId,\n cols: this.headlessTerminal.cols,\n rows: this.headlessTerminal.rows,\n data,\n outputSeq: this.outputSeq,\n requestId: msg.requestId,\n }),\n );\n log.info(\n {\n sessionId: this.sessionId,\n cols: this.headlessTerminal.cols,\n rows: this.headlessTerminal.rows,\n bytes: data.length,\n },\n \"Snapshot sent via IPC\",\n );\n }\n }\n });\n\n this.socket.on(\"close\", () => {\n log.info(\"Serve socket closed\");\n if (this.remoteDetached) {\n log.info(\"Remote view detached, skipping serve reconnect\");\n return;\n }\n if (!this.fsm.isIn([TerminalState.RECONNECTING, TerminalState.EXITED])) {\n this.fsm.transitionTo(TerminalState.RECONNECTING);\n this.reconnectToServe();\n }\n });\n\n // socket error 通常和 close 成对出现;这里只记 warn 避免静默吞错,重连仍由 close handler 触发\n this.socket.on(\"error\", (err) => {\n log.warn({ err: err.message }, \"Serve socket error\");\n });\n }\n\n // 超过 IDLE_THRESHOLD_MS 无 PTY 输出则从 working 翻回 turn_complete\n private setupIdleCheck(): void {\n if (this.idleCheckTimer) clearInterval(this.idleCheckTimer);\n this.idleCheckTimer = setInterval(() => {\n if (this.lastOutputTime > 0 && Date.now() - this.lastOutputTime > IDLE_THRESHOLD_MS) {\n this.lastOutputTime = 0;\n if (this.currentPtyState === \"working\") {\n this.currentPtyState = \"turn_complete\";\n this.sendPtyState(\"turn_complete\");\n }\n }\n }, IDLE_CHECK_INTERVAL_MS);\n }\n\n private async reconnectToServe(): Promise<void> {\n log.info(\"Serve connection lost, starting reconnection\");\n\n // 两条路径都不该再继续 spawn daemon:\n // - STOPPED=true:用户主动 dev-anywhere stop,不要对抗用户意图。\n // - consecutiveSpawnFailures 跨过阈值:说明环境有持续性问题,spawn 再多也白搭。\n // 进入 passive 后仅做 tryConnect 等待,daemon 起来或用户 dev-anywhere start 后自动恢复。\n let consecutiveSpawnFailures = 0;\n\n for (let i = 0; ; i++) {\n if (this.remoteDetached) return;\n await sleep(Math.min(RECONNECT_INITIAL_DELAY_MS * (i + 1), RECONNECT_MAX_DELAY_MS));\n\n const stopped = existsSync(STOPPED_PATH);\n const degraded = consecutiveSpawnFailures >= SPAWN_FAILURE_THRESHOLD;\n const passive = stopped || degraded;\n\n try {\n log.debug({ attempt: i + 1, stopped, degraded }, \"Reconnect attempt\");\n const newSocket = passive ? await tryConnect(SOCK_PATH) : await ensureService();\n if (!newSocket) continue;\n\n if (degraded) notifyUser(\"serve daemon reachable, reconnected\");\n consecutiveSpawnFailures = 0;\n\n this.socket = newSocket;\n log.info({ attempt: i + 1, sessionId: this.sessionId }, \"Reconnected to serve\");\n\n this.setupSocketHandlers();\n\n if (this.sessionId) {\n this.fsm.transitionTo(TerminalState.CREATING_SESSION);\n this.socket.write(\n serializeIpc({\n type: \"session_create_request\",\n mode: \"pty\",\n provider: this.provider.id,\n cwd: this.sessionCwd,\n name: tildify(this.sessionCwd),\n pid: process.pid,\n sessionId: this.sessionId,\n }),\n );\n const resp = await waitForMessage(this.socket, \"session_create_response\");\n if (!resp.error) {\n this.sessionId = resp.sessionId;\n this.socket.write(\n serializeIpc({ type: \"pty_register\", sessionId: this.sessionId, pid: process.pid }),\n );\n this.replayCurrentPtyState();\n this.fsm.transitionTo(TerminalState.RUNNING);\n log.info({ sessionId: this.sessionId }, \"Session re-registered after reconnect\");\n }\n } else {\n this.fsm.transitionTo(TerminalState.RUNNING);\n }\n\n return;\n } catch (err) {\n // passive 模式走 tryConnect,失败返回 null 不抛;这里只可能是 ensureService spawn 失败\n if (!passive) {\n consecutiveSpawnFailures++;\n if (consecutiveSpawnFailures === SPAWN_FAILURE_THRESHOLD) {\n notifyUser(\n `serve daemon spawn failed ${SPAWN_FAILURE_THRESHOLD}x — auto-spawn disabled; check environment or run 'dev-anywhere start'`,\n );\n }\n }\n log.debug(\n { err: err instanceof Error ? err.message : err, attempt: i + 1, degraded },\n \"Reconnect attempt failed\",\n );\n }\n }\n }\n\n private detachRemoteView(): void {\n const sessionId = this.sessionId;\n if (!sessionId) return;\n this.remoteDetached = true;\n this.sessionId = null;\n this.hookContext = null;\n this.currentPtyState = \"turn_complete\";\n log.info({ sessionId }, \"Remote view detached; local PTY keeps running\");\n notifyUser(\"remote viewing detached\");\n if (this.socket.writable) this.socket.end();\n }\n}\n\nfunction providerFromEnv(): ProviderId {\n return process.env.DEV_ANYWHERE_PROVIDER === \"codex\" ? \"codex\" : \"claude\";\n}\n\nexport async function startTerminal(\n providerArgs: string[],\n providerId: ProviderId = providerFromEnv(),\n): Promise<void> {\n await new TerminalSession(PROVIDERS[providerId], providerArgs).run();\n}\n","// 读 stdout cols/rows,非 TTY 抛错。\nexport function readTtySize(stream: NodeJS.WriteStream): { cols: number; rows: number } {\n const { columns, rows } = stream;\n if (columns === undefined || rows === undefined) {\n throw new Error(\n \"stdout is not an interactive TTY (columns/rows undefined); dev-anywhere requires running in a real terminal\",\n );\n }\n return { cols: columns, rows };\n}\n\n// 发一条 OSC 9 iTerm2-style 系统通知 + 响铃。iTerm2 / kitty / wezterm 等会弹出带 message\n// 的系统通知;不认 OSC 9 的终端会忽略转义序列只剩下 BEL 响铃。\n// 用此而非 stderr banner 的原因:dev-anywhere 对 Claude PTY 画面保持透明是硬约束,\n// banner 会挤掉 Claude 的渲染行,OSC 9 不占画面,BEL 是纯听觉信号。\nexport function notifyUser(message: string): void {\n process.stderr.write(`\\x1b]9;${message}\\x07`);\n}\n\n// Provider TUI 可能开启 bracketed paste、application cursor/keypad、mouse tracking、\n// xterm modifyOtherKeys 或 kitty keyboard protocol。若 provider 被远程终止或异常退出,\n// 这些模式可能来不及自行恢复,外层 shell 会把 Ctrl-C 显示成 \";5;99~\" 一类残留序列。\nexport function restoreHostTerminalModes(stream: NodeJS.WriteStream): void {\n if (!stream.isTTY) return;\n const restoreSequences = [\n \"\\x1b[?1l\", // application cursor keys off\n \"\\x1b>\", // application keypad off\n \"\\x1b[?1000l\",\n \"\\x1b[?1002l\",\n \"\\x1b[?1003l\",\n \"\\x1b[?1004l\",\n \"\\x1b[?1006l\",\n \"\\x1b[?1015l\",\n \"\\x1b[?2004l\", // bracketed paste off\n \"\\x1b[>4;0m\", // xterm modifyOtherKeys off\n \"\\x1b[<u\", // kitty keyboard protocol off\n ].join(\"\");\n stream.write(restoreSequences);\n}\n","import * as pty from \"node-pty\";\nimport type { IPty } from \"node-pty\";\nimport type { ProviderAdapter, ProviderHookContext } from \"../providers/index.js\";\nimport { readTtySize, restoreHostTerminalModes } from \"./tty.js\";\n\ninterface PtyManagerOptions {\n provider: ProviderAdapter;\n providerArgs: string[];\n cwd: string;\n hook?: ProviderHookContext;\n tap: (data: string) => void;\n stdin: NodeJS.ReadStream;\n stdout: NodeJS.WriteStream;\n onSessionExit?: (code: number) => void | Promise<void>;\n onResize?: (cols: number, rows: number) => void;\n}\n\nexport class PtyManager {\n private child: IPty | null = null;\n private readonly provider: ProviderAdapter;\n private readonly providerArgs: string[];\n private readonly cwd: string;\n private readonly hook?: ProviderHookContext;\n private readonly tap: (data: string) => void;\n private readonly stdin: NodeJS.ReadStream;\n private readonly stdout: NodeJS.WriteStream;\n private readonly onSessionExit?: (code: number) => void;\n private readonly onResize?: (cols: number, rows: number) => void;\n\n constructor(options: PtyManagerOptions) {\n this.provider = options.provider;\n this.providerArgs = options.providerArgs;\n this.cwd = options.cwd;\n this.hook = options.hook;\n this.tap = options.tap;\n this.stdin = options.stdin;\n this.stdout = options.stdout;\n this.onSessionExit = options.onSessionExit;\n this.onResize = options.onResize;\n }\n\n start(): void {\n const { cols, rows } = readTtySize(this.stdout);\n\n const command = this.provider.buildTerminalCommand(\n { args: this.providerArgs, hook: this.hook },\n process.env,\n );\n const child = pty.spawn(command.command, command.args, {\n name: process.env.TERM ?? \"xterm-256color\",\n cols,\n rows,\n cwd: this.cwd,\n env: command.env as Record<string, string>,\n });\n this.child = child;\n\n // raw mode 仅在 stdin 为 TTY 时开启\n const isInteractive = this.stdin.isTTY === true;\n if (isInteractive) {\n this.stdin.setRawMode(true);\n }\n this.stdin.resume();\n\n // stdin -> PTY\n this.stdin.on(\"data\", (data: Buffer) => {\n child.write(data.toString());\n });\n\n // PTY -> stdout + tap\n child.onData((data: string) => this.handleData(data));\n\n // resize 防抖,50ms 窗口合并快速连续的尺寸变化\n let resizeTimer: ReturnType<typeof setTimeout> | null = null;\n this.stdout.on(\"resize\", () => {\n if (resizeTimer) clearTimeout(resizeTimer);\n resizeTimer = setTimeout(() => {\n const { cols: newCols, rows: newRows } = readTtySize(this.stdout);\n child.resize(newCols, newRows);\n this.onResize?.(newCols, newRows);\n }, 50);\n });\n\n // 子进程退出,按 Unix 惯例处理信号退出码,通过回调通知调用方\n child.onExit(({ exitCode, signal }) => {\n if (isInteractive) {\n try {\n this.stdin.setRawMode(false);\n } catch {\n // stdin 可能已关闭\n }\n }\n restoreHostTerminalModes(this.stdout);\n const code = signal ? 128 + signal : exitCode;\n this.onSessionExit?.(code);\n });\n\n // stdin 结束时写入 EOF 控制字符到 PTY\n this.stdin.on(\"end\", () => {\n child.write(\"\\x04\");\n });\n }\n\n /**\n * PTY 数据到达时的统一处理:OSC 9 修复 + 输出到终端 + 传给 tap\n */\n private handleData(data: string): void {\n // PTY 的 onlcr 会把 OSC 序列里的 \\n 转成 \\r\\n,还原为 \\n\n const fixed = data.replace(\n // eslint-disable-next-line no-control-regex\n /\\x1b\\]9;([\\s\\S]*?)\\x07/g,\n (_, content: string) => `\\x1b]9;${content.replace(/\\r\\n/g, \"\\n\")}\\x07`,\n );\n this.stdout.write(fixed);\n this.tap(data);\n }\n\n // 向 PTY 子进程写入数据,用于远程输入注入\n write(data: string): void {\n this.child?.write(data);\n }\n\n cleanup(exitCode: number): void {\n if (this.stdin.isTTY) {\n try {\n this.stdin.setRawMode(false);\n } catch {\n // stdin 可能已关闭\n }\n }\n restoreHostTerminalModes(this.stdout);\n if (this.child) {\n try {\n this.child.kill();\n } catch {\n // 子进程可能已退出\n }\n }\n this.onSessionExit?.(exitCode);\n }\n}\n","import { statSync } from \"node:fs\";\n\nfunction isDirectory(path: string | undefined): path is string {\n if (!path) return false;\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function resolveTerminalCwd(env: NodeJS.ProcessEnv = process.env): string {\n const candidates = [env.DEV_ANYWHERE_CWD, env.INIT_CWD, env.PWD, process.cwd()];\n return candidates.find(isDirectory) ?? process.cwd();\n}\n","import type { Socket } from \"node:net\";\nimport { createFSM } from \"../common/state-machine.js\";\nimport { serializeIpc } from \"../ipc/ipc-protocol.js\";\n\n// terminal 进程生命周期状态\nexport const TerminalState = {\n INIT: \"init\",\n CONNECTING_SERVICE: \"connecting_service\",\n CREATING_SESSION: \"creating_session\",\n RUNNING: \"running\",\n RECONNECTING: \"reconnecting\",\n EXITED: \"exited\",\n} as const;\nexport type TerminalState = (typeof TerminalState)[keyof typeof TerminalState];\n\n// 允许的状态转换。CREATING_SESSION/RUNNING 下可被 socket close 打断进入 RECONNECTING;\n// 任意非终态都可能被 PTY 退出或 SIGTERM 打断进入 EXITED。\nexport const TERMINAL_TRANSITIONS: Record<TerminalState, readonly TerminalState[]> = {\n init: [\"connecting_service\"],\n connecting_service: [\"creating_session\", \"exited\"],\n creating_session: [\"running\", \"reconnecting\", \"exited\"],\n running: [\"reconnecting\", \"exited\"],\n reconnecting: [\"creating_session\", \"running\", \"exited\"],\n exited: [],\n};\n\n// 下面三个依赖是 getter 而非值:因为它们在 terminal.ts 里是 let 变量,在 handler 创建之后还会变——\n// socket 在 reconnect 时被重新赋值为新实例,sessionId 在 session_create 成功后才有值,\n// idleCheckTimer 在 setupIdleCheck 跑完才赋值。直接传值只会记录 handler 构造那一刻的旧值。\ninterface ExitHandlerDeps {\n fsm: ReturnType<typeof createFSM<TerminalState>>;\n getSocket: () => Socket;\n getSessionId: () => string | null;\n getIdleCheckTimer: () => NodeJS.Timeout | null;\n // 测试注入点,production 默认 process.exit\n exit?: (code: number) => void;\n}\n\n// 构造统一的收尾函数:转 EXITED → 停 idle 定时器 → 给 serve 发 pty_deregister → 退进程。\n// onSessionExit 与 SIGTERM handler 共享同一实例;Ctrl+C 两连击或 PTY 退出与 SIGTERM 竞争时,\n// 第二次调用通过 fsm EXITED 检查直接短路。\nexport function createExitHandler(deps: ExitHandlerDeps): (code: number) => void {\n const exit = deps.exit ?? ((code: number) => process.exit(code));\n return (code: number) => {\n if (deps.fsm.is(TerminalState.EXITED)) return;\n deps.fsm.transitionTo(TerminalState.EXITED);\n const timer = deps.getIdleCheckTimer();\n if (timer) clearInterval(timer);\n const socket = deps.getSocket();\n const sessionId = deps.getSessionId();\n if (socket.writable && sessionId) {\n socket.end(serializeIpc({ type: \"pty_deregister\", sessionId }), () => exit(code));\n } else {\n exit(code);\n }\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/terminal.ts","../src/terminal/tty.ts","../src/terminal/pty-manager.ts","../src/terminal/cwd.ts","../src/terminal/state.ts"],"sourcesContent":["import { connect, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { readTtySize, notifyUser } from \"./terminal/tty.js\";\nimport { PtyManager } from \"./terminal/pty-manager.js\";\nimport { resolveTerminalCwd } from \"./terminal/cwd.js\";\nimport pkg from \"@xterm/headless\";\nconst { Terminal: HeadlessTerminal } = pkg;\nimport { SerializeAddon } from \"@xterm/addon-serialize\";\nimport { UnicodeGraphemesAddon } from \"@xterm/addon-unicode-graphemes\";\nimport {\n extractOscSequences,\n extractOscSignals,\n type PtySemanticState,\n} from \"./common/osc-extractor.js\";\nimport {\n shouldReleaseApprovalWait,\n stateAfterApprovalRelease,\n} from \"./common/pty-approval-state.js\";\nimport { TerminalState, TERMINAL_TRANSITIONS, createExitHandler } from \"./terminal/state.js\";\nimport {\n SOCK_PATH,\n STOPPED_PATH,\n SERVICE_LOG_PATH,\n PROFILE_NAME,\n tildify,\n} from \"./common/paths.js\";\nimport { spawnScript } from \"./common/env.js\";\nimport { daemonRelayArgs } from \"./common/daemon-env.js\";\nimport {\n createIpcReader,\n serializeIpc,\n encodeBinaryIpcFrame,\n type IpcMessage,\n} from \"./ipc/ipc-protocol.js\";\nimport { terminalLogger as log } from \"./common/logger.js\";\nimport { createFSM } from \"./common/state-machine.js\";\nimport {\n CLAUDE_PROVIDER,\n CODEX_PROVIDER,\n type ProviderAdapter,\n type ProviderHookContext,\n type ProviderId,\n} from \"./providers/index.js\";\n\n// serve daemon 自动拉起的连接重试参数\nconst ENSURE_SERVICE_MAX_RETRIES = 20;\nconst ENSURE_SERVICE_INITIAL_DELAY_MS = 100;\nconst ENSURE_SERVICE_MAX_DELAY_MS = 2_000;\n\n// 等待特定类型 IPC 消息的默认超时\nconst WAIT_FOR_MESSAGE_TIMEOUT_MS = 10_000;\n\n// idle 检测:超过 IDLE_THRESHOLD_MS 无输出则翻转 working -> turn_complete\nconst IDLE_CHECK_INTERVAL_MS = 3_000;\nconst IDLE_THRESHOLD_MS = 3_000;\n\n// serve 连接断开后的重连重试参数\nconst RECONNECT_INITIAL_DELAY_MS = 1_000;\nconst RECONNECT_MAX_DELAY_MS = 5_000;\n// 连续 spawn 失败到达阈值后停止自动 spawn,降为被动 tryConnect 轮询。\n// 作用:环境异常(端口占用、依赖缺失、权限不足)时避免反复拉起短命子进程把日志刷爆。\nconst SPAWN_FAILURE_THRESHOLD = 3;\n\nconst PROVIDERS: Record<ProviderId, ProviderAdapter> = {\n claude: CLAUDE_PROVIDER,\n codex: CODEX_PROVIDER,\n};\n\nfunction tryConnect(sockPath: string): Promise<Socket | null> {\n return new Promise((resolve) => {\n const s = connect(sockPath);\n s.on(\"connect\", () => resolve(s));\n s.on(\"error\", () => resolve(null));\n });\n}\n\nasync function ensureService(autoStart = true): Promise<Socket> {\n const existing = await tryConnect(SOCK_PATH);\n if (existing) {\n log.info(\"Connected to existing service\");\n return existing;\n }\n\n if (!autoStart) throw new Error(\"Service is not running\");\n\n if (existsSync(STOPPED_PATH)) unlinkSync(STOPPED_PATH);\n\n log.info(\"Auto-starting serve daemon\");\n const child = spawnScript(\n new URL(\"./serve\", import.meta.url),\n [\"--profile\", PROFILE_NAME, ...daemonRelayArgs()],\n {\n env: { ...process.env },\n logger: log,\n },\n );\n\n // 监听 daemon 失败信号,让下面的 tryConnect 轮询能在 daemon 启动时就崩的场景下立刻抛诊断。\n // - 'exit':进程启动成功后又退出(配置错误、端口占用、内部崩溃),带 code/signal。\n // - 'error':spawn 本身失败(ENOENT 找不到 tsx/node 等),Node 文档说此时 'exit' may or may not 跟着 fire,\n // 所以显式监听补完备性。spawnScript 内部另装了一对只管日志的监听器,跟这里互不影响。\n let childFailed = false;\n let exitCode: number | null = null;\n let exitSignal: NodeJS.Signals | null = null;\n let spawnError: Error | null = null;\n child.once(\"exit\", (code, signal) => {\n childFailed = true;\n exitCode = code;\n exitSignal = signal;\n });\n child.once(\"error\", (err) => {\n childFailed = true;\n spawnError = err;\n });\n\n for (let i = 0; i < ENSURE_SERVICE_MAX_RETRIES; i++) {\n const delay = Math.min(ENSURE_SERVICE_INITIAL_DELAY_MS * (i + 1), ENSURE_SERVICE_MAX_DELAY_MS);\n await sleep(delay);\n\n if (childFailed) {\n log.error(\n { code: exitCode, signal: exitSignal, err: spawnError && String(spawnError) },\n \"Serve daemon failed to start\",\n );\n const detail = spawnError\n ? `spawn error=${String(spawnError)}`\n : `code=${exitCode}, signal=${exitSignal}`;\n throw new Error(\n `Serve daemon failed to start (${detail}). Check ${SERVICE_LOG_PATH} for details.`,\n );\n }\n\n const socket = await tryConnect(SOCK_PATH);\n if (socket) {\n log.info({ attempt: i + 1 }, \"Connected to service after retry\");\n return socket;\n }\n }\n\n log.error({ maxRetries: ENSURE_SERVICE_MAX_RETRIES }, \"Failed to connect to service\");\n throw new Error(\n `Failed to connect to dev-anywhere service after ${ENSURE_SERVICE_MAX_RETRIES} retries. Check ${SERVICE_LOG_PATH} for details.`,\n );\n}\n\nfunction waitForMessage<T extends IpcMessage[\"type\"]>(\n socket: Socket,\n messageType: T,\n): Promise<Extract<IpcMessage, { type: T }>> {\n return new Promise((resolve, reject) => {\n let timeout: NodeJS.Timeout | null = null;\n const dispose = createIpcReader(socket, (msg: IpcMessage) => {\n if (msg.type === messageType) {\n if (timeout) clearTimeout(timeout);\n dispose();\n resolve(msg as Extract<IpcMessage, { type: T }>);\n }\n });\n timeout = setTimeout(() => {\n dispose();\n reject(new Error(`Timeout waiting for ${messageType}`));\n }, WAIT_FOR_MESSAGE_TIMEOUT_MS);\n });\n}\n\nclass TerminalSession {\n private readonly fsm = createFSM<TerminalState>({\n initial: TerminalState.INIT,\n transitions: TERMINAL_TRANSITIONS,\n onTransition: (from, to) => log.info({ from, to }, \"Terminal state transition\"),\n });\n private readonly sessionCwd = resolveTerminalCwd();\n // socket 在 run() 中连上 serve 后首次赋值;reconnect 会重新赋值为新实例\n private socket!: Socket;\n private sessionId: string | null = null;\n private hookContext: ProviderHookContext | null = null;\n private ptyManager: PtyManager | null = null;\n private lastOutputTime = 0;\n private idleCheckTimer: NodeJS.Timeout | null = null;\n private currentPtyState: PtySemanticState = \"turn_complete\";\n // headless terminal 在本进程维护,用于按需 serialize() 给远程 client\n private headlessTerminal: InstanceType<typeof HeadlessTerminal> | null = null;\n private serializeAddon: SerializeAddon | null = null;\n private outputSeq = 0;\n private remoteDetached = false;\n // 记录上次 bridge 状态避免重连抖动导致 banner 连刷;初值 null 让首次状态(无论真假)都打,启动时提示 remote viewing 是否就绪\n private lastBridgeConnected: boolean | null = null;\n // 收尾函数在 run() 里创建一次,PTY 退出与 SIGTERM 共用;内部通过 fsm EXITED 检查短路\n private cleanupAndExit!: (code: number) => void;\n\n constructor(\n private readonly provider: ProviderAdapter,\n private readonly providerArgs: string[],\n ) {}\n\n async run(): Promise<void> {\n log.info(\"Terminal starting\");\n this.fsm.transitionTo(TerminalState.CONNECTING_SERVICE);\n this.socket = await ensureService();\n\n await this.createSession();\n this.initHeadlessTerminal();\n this.cleanupAndExit = createExitHandler({\n fsm: this.fsm,\n getSocket: () => this.socket,\n getSessionId: () => this.sessionId,\n getIdleCheckTimer: () => this.idleCheckTimer,\n });\n\n this.setupSocketHandlers();\n this.startPtyManager();\n\n this.socket.write(\n serializeIpc({ type: \"pty_register\", sessionId: this.sessionId!, pid: process.pid }),\n );\n this.replayCurrentPtyState();\n this.fsm.transitionTo(TerminalState.RUNNING);\n this.setupIdleCheck();\n\n process.on(\"SIGTERM\", () => {\n log.info({ sessionId: this.sessionId }, \"SIGTERM received, shutting down\");\n this.cleanupAndExit(143);\n });\n }\n\n private async createSession(): Promise<void> {\n this.fsm.transitionTo(TerminalState.CREATING_SESSION);\n const responsePromise = waitForMessage(this.socket, \"session_create_response\");\n this.socket.write(\n serializeIpc({\n type: \"session_create_request\",\n mode: \"pty\",\n provider: this.provider.id,\n cwd: this.sessionCwd,\n name: tildify(this.sessionCwd),\n pid: process.pid,\n }),\n );\n const response = await responsePromise;\n if (response.error) {\n throw new Error(`Failed to create session: ${response.error}`);\n }\n this.sessionId = response.sessionId;\n this.hookContext = response.hook ?? null;\n }\n\n private initHeadlessTerminal(): void {\n const { cols, rows } = readTtySize(process.stdout);\n log.info(\n { sessionId: this.sessionId, cols, rows },\n \"Session created, initializing headless terminal\",\n );\n this.headlessTerminal = new HeadlessTerminal({\n cols,\n rows,\n scrollback: 5000,\n allowProposedApi: true,\n });\n this.serializeAddon = new SerializeAddon();\n // UnicodeGraphemesAddon activate() 里会设置 activeVersion = '15-graphemes'\n this.headlessTerminal.loadAddon(this.serializeAddon);\n this.headlessTerminal.loadAddon(new UnicodeGraphemesAddon());\n }\n\n private startPtyManager(): void {\n this.ptyManager = new PtyManager({\n provider: this.provider,\n providerArgs: this.providerArgs,\n cwd: this.sessionCwd,\n hook: this.hookContext ?? undefined,\n tap: (data) => this.handlePtyData(data),\n stdin: process.stdin,\n stdout: process.stdout,\n onResize: (newCols, newRows) => {\n if (this.headlessTerminal) this.headlessTerminal.resize(newCols, newRows);\n if (this.socket.writable && this.sessionId) {\n this.socket.write(\n serializeIpc({\n type: \"pty_resize\",\n sessionId: this.sessionId,\n cols: newCols,\n rows: newRows,\n }),\n );\n }\n },\n onSessionExit: (code: number) => {\n log.info({ sessionId: this.sessionId, exitCode: code }, \"PTY exited, cleaning up\");\n this.cleanupAndExit(code);\n },\n });\n this.ptyManager.start();\n log.info({ sessionId: this.sessionId }, \"PTY started with headless terminal\");\n }\n\n // PTY 的每一帧输出都要:追到 headless terminal 状态、推 binary IPC、提取 provider 语义事件\n private handlePtyData(data: string): void {\n this.lastOutputTime = Date.now();\n this.outputSeq += 1;\n\n if (this.headlessTerminal) this.headlessTerminal.write(data);\n\n if (!this.remoteDetached && this.socket.writable && this.sessionId) {\n this.socket.write(\n encodeBinaryIpcFrame(this.sessionId, Buffer.from(data, \"utf-8\"), this.outputSeq),\n );\n }\n\n const oscSequences = extractOscSequences(data);\n const signal = extractOscSignals(data, this.provider.id);\n if (oscSequences.length > 0) {\n log.debug(\n {\n sessionId: this.sessionId,\n oscSequences,\n signal,\n },\n \"PTY OSC sequences parsed\",\n );\n }\n if (signal?.title) {\n this.sendTerminalTitle(signal.title);\n }\n if (signal?.state === \"approval_wait\") {\n this.currentPtyState = \"approval_wait\";\n this.sendPtyState(\"approval_wait\", { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (\n shouldReleaseApprovalWait({\n currentState: this.currentPtyState,\n signalState: signal?.state,\n })\n ) {\n const nextState = stateAfterApprovalRelease(signal?.state);\n this.currentPtyState = nextState;\n this.sendPtyState(nextState, { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (this.currentPtyState === \"approval_wait\" && signal?.state !== \"turn_complete\") {\n this.sendPtyState(\"approval_wait\", { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (signal && signal.state !== \"working\") {\n this.currentPtyState = signal.state;\n this.sendPtyState(signal.state, { title: signal.title, tool: signal.tool });\n return;\n }\n if (this.currentPtyState !== \"working\") {\n this.currentPtyState = \"working\";\n this.sendPtyState(\"working\");\n }\n }\n\n private sendTerminalTitle(title: string): void {\n if (this.remoteDetached || !this.socket.writable || !this.sessionId) return;\n this.socket.write(\n serializeIpc({\n type: \"pty_title_change\",\n sessionId: this.sessionId,\n title,\n }),\n );\n }\n\n private sendPtyState(state: PtySemanticState, meta?: { title?: string; tool?: string }): void {\n if (this.remoteDetached || !this.socket.writable || !this.sessionId) return;\n this.socket.write(\n serializeIpc({\n type: \"pty_semantic_event\",\n sessionId: this.sessionId,\n state,\n ...(meta?.title !== undefined ? { title: meta.title } : {}),\n ...(meta?.tool !== undefined ? { tool: meta.tool } : {}),\n }),\n );\n log.info(\n { sessionId: this.sessionId, state, title: meta?.title, tool: meta?.tool },\n \"PTY semantic event pushed\",\n );\n }\n\n private replayCurrentPtyState(): void {\n if (this.currentPtyState === \"turn_complete\") return;\n this.sendPtyState(this.currentPtyState);\n }\n\n private handleBridgeStatus(connected: boolean): void {\n if (this.remoteDetached) return;\n if (this.lastBridgeConnected === connected) return;\n this.lastBridgeConnected = connected;\n log.info({ connected }, \"Bridge status changed, notifying user\");\n notifyUser(connected ? \"relay online\" : \"relay offline — remote viewing unavailable\");\n }\n\n private setupSocketHandlers(): void {\n createIpcReader(this.socket, (msg: IpcMessage) => {\n if (msg.type === \"pty_input\" && msg.sessionId === this.sessionId) {\n log.debug({ sessionId: this.sessionId, bytes: msg.data.length }, \"Remote input received\");\n this.ptyManager?.write(msg.data);\n } else if (msg.type === \"pty_detach\" && msg.sessionId === this.sessionId) {\n this.detachRemoteView();\n } else if (msg.type === \"bridge_status\") {\n this.handleBridgeStatus(msg.connected);\n } else if (msg.type === \"pty_subscribe\" && msg.sessionId === this.sessionId) {\n if (this.serializeAddon && this.headlessTerminal) {\n const data = this.serializeAddon.serialize();\n this.socket.write(\n serializeIpc({\n type: \"pty_snapshot\",\n sessionId: msg.sessionId,\n cols: this.headlessTerminal.cols,\n rows: this.headlessTerminal.rows,\n data,\n outputSeq: this.outputSeq,\n requestId: msg.requestId,\n }),\n );\n log.info(\n {\n sessionId: this.sessionId,\n cols: this.headlessTerminal.cols,\n rows: this.headlessTerminal.rows,\n bytes: data.length,\n },\n \"Snapshot sent via IPC\",\n );\n }\n }\n });\n\n this.socket.on(\"close\", () => {\n log.info(\"Serve socket closed\");\n if (this.remoteDetached) {\n log.info(\"Remote view detached, skipping serve reconnect\");\n return;\n }\n if (!this.fsm.isIn([TerminalState.RECONNECTING, TerminalState.EXITED])) {\n this.fsm.transitionTo(TerminalState.RECONNECTING);\n this.reconnectToServe();\n }\n });\n\n // socket error 通常和 close 成对出现;这里只记 warn 避免静默吞错,重连仍由 close handler 触发\n this.socket.on(\"error\", (err) => {\n log.warn({ err: err.message }, \"Serve socket error\");\n });\n }\n\n // 超过 IDLE_THRESHOLD_MS 无 PTY 输出则从 working 翻回 turn_complete\n private setupIdleCheck(): void {\n if (this.idleCheckTimer) clearInterval(this.idleCheckTimer);\n this.idleCheckTimer = setInterval(() => {\n if (this.lastOutputTime > 0 && Date.now() - this.lastOutputTime > IDLE_THRESHOLD_MS) {\n this.lastOutputTime = 0;\n if (this.currentPtyState === \"working\") {\n this.currentPtyState = \"turn_complete\";\n this.sendPtyState(\"turn_complete\");\n }\n }\n }, IDLE_CHECK_INTERVAL_MS);\n }\n\n private async reconnectToServe(): Promise<void> {\n log.info(\"Serve connection lost, starting reconnection\");\n\n // 两条路径都不该再继续 spawn daemon:\n // - STOPPED=true:用户主动 dev-anywhere stop,不要对抗用户意图。\n // - consecutiveSpawnFailures 跨过阈值:说明环境有持续性问题,spawn 再多也白搭。\n // 进入 passive 后仅做 tryConnect 等待,daemon 起来或用户 dev-anywhere start 后自动恢复。\n let consecutiveSpawnFailures = 0;\n\n for (let i = 0; ; i++) {\n if (this.remoteDetached) return;\n await sleep(Math.min(RECONNECT_INITIAL_DELAY_MS * (i + 1), RECONNECT_MAX_DELAY_MS));\n\n const stopped = existsSync(STOPPED_PATH);\n const degraded = consecutiveSpawnFailures >= SPAWN_FAILURE_THRESHOLD;\n const passive = stopped || degraded;\n\n try {\n log.debug({ attempt: i + 1, stopped, degraded }, \"Reconnect attempt\");\n const newSocket = passive ? await tryConnect(SOCK_PATH) : await ensureService();\n if (!newSocket) continue;\n\n if (degraded) notifyUser(\"serve daemon reachable, reconnected\");\n consecutiveSpawnFailures = 0;\n\n this.socket = newSocket;\n log.info({ attempt: i + 1, sessionId: this.sessionId }, \"Reconnected to serve\");\n\n this.setupSocketHandlers();\n\n if (this.sessionId) {\n this.fsm.transitionTo(TerminalState.CREATING_SESSION);\n this.socket.write(\n serializeIpc({\n type: \"session_create_request\",\n mode: \"pty\",\n provider: this.provider.id,\n cwd: this.sessionCwd,\n name: tildify(this.sessionCwd),\n pid: process.pid,\n sessionId: this.sessionId,\n }),\n );\n const resp = await waitForMessage(this.socket, \"session_create_response\");\n if (!resp.error) {\n this.sessionId = resp.sessionId;\n this.socket.write(\n serializeIpc({ type: \"pty_register\", sessionId: this.sessionId, pid: process.pid }),\n );\n this.replayCurrentPtyState();\n this.fsm.transitionTo(TerminalState.RUNNING);\n log.info({ sessionId: this.sessionId }, \"Session re-registered after reconnect\");\n }\n } else {\n this.fsm.transitionTo(TerminalState.RUNNING);\n }\n\n return;\n } catch (err) {\n // passive 模式走 tryConnect,失败返回 null 不抛;这里只可能是 ensureService spawn 失败\n if (!passive) {\n consecutiveSpawnFailures++;\n if (consecutiveSpawnFailures === SPAWN_FAILURE_THRESHOLD) {\n notifyUser(\n `serve daemon spawn failed ${SPAWN_FAILURE_THRESHOLD}x — auto-spawn disabled; check environment or run 'dev-anywhere start'`,\n );\n }\n }\n log.debug(\n { err: err instanceof Error ? err.message : err, attempt: i + 1, degraded },\n \"Reconnect attempt failed\",\n );\n }\n }\n }\n\n private detachRemoteView(): void {\n const sessionId = this.sessionId;\n if (!sessionId) return;\n this.remoteDetached = true;\n this.sessionId = null;\n this.hookContext = null;\n this.currentPtyState = \"turn_complete\";\n log.info({ sessionId }, \"Remote view detached; local PTY keeps running\");\n notifyUser(\"remote viewing detached\");\n if (this.socket.writable) this.socket.end();\n }\n}\n\nfunction providerFromEnv(): ProviderId {\n return process.env.DEV_ANYWHERE_PROVIDER === \"codex\" ? \"codex\" : \"claude\";\n}\n\nexport async function startTerminal(\n providerArgs: string[],\n providerId: ProviderId = providerFromEnv(),\n): Promise<void> {\n await new TerminalSession(PROVIDERS[providerId], providerArgs).run();\n}\n","// 读 stdout cols/rows,非 TTY 抛错。\nexport function readTtySize(stream: NodeJS.WriteStream): { cols: number; rows: number } {\n const { columns, rows } = stream;\n if (columns === undefined || rows === undefined) {\n throw new Error(\n \"stdout is not an interactive TTY (columns/rows undefined); dev-anywhere requires running in a real terminal\",\n );\n }\n return { cols: columns, rows };\n}\n\n// 发一条 OSC 9 iTerm2-style 系统通知 + 响铃。iTerm2 / kitty / wezterm 等会弹出带 message\n// 的系统通知;不认 OSC 9 的终端会忽略转义序列只剩下 BEL 响铃。\n// 用此而非 stderr banner 的原因:dev-anywhere 对 Claude PTY 画面保持透明是硬约束,\n// banner 会挤掉 Claude 的渲染行,OSC 9 不占画面,BEL 是纯听觉信号。\nexport function notifyUser(message: string): void {\n process.stderr.write(`\\x1b]9;${message}\\x07`);\n}\n\n// Provider TUI 可能开启 bracketed paste、application cursor/keypad、mouse tracking、\n// xterm modifyOtherKeys 或 kitty keyboard protocol。若 provider 被远程终止或异常退出,\n// 这些模式可能来不及自行恢复,外层 shell 会把 Ctrl-C 显示成 \";5;99~\" 一类残留序列。\nexport function restoreHostTerminalModes(stream: NodeJS.WriteStream): void {\n if (!stream.isTTY) return;\n const restoreSequences = [\n \"\\x1b[?1l\", // application cursor keys off\n \"\\x1b>\", // application keypad off\n \"\\x1b[?1000l\",\n \"\\x1b[?1002l\",\n \"\\x1b[?1003l\",\n \"\\x1b[?1004l\",\n \"\\x1b[?1006l\",\n \"\\x1b[?1015l\",\n \"\\x1b[?2004l\", // bracketed paste off\n \"\\x1b[>4;0m\", // xterm modifyOtherKeys off\n \"\\x1b[<u\", // kitty keyboard protocol off\n ].join(\"\");\n stream.write(restoreSequences);\n}\n","import * as pty from \"node-pty\";\nimport type { IPty } from \"node-pty\";\nimport type { ProviderAdapter, ProviderHookContext } from \"../providers/index.js\";\nimport { readTtySize, restoreHostTerminalModes } from \"./tty.js\";\n\ninterface PtyManagerOptions {\n provider: ProviderAdapter;\n providerArgs: string[];\n cwd: string;\n hook?: ProviderHookContext;\n tap: (data: string) => void;\n stdin: NodeJS.ReadStream;\n stdout: NodeJS.WriteStream;\n onSessionExit?: (code: number) => void | Promise<void>;\n onResize?: (cols: number, rows: number) => void;\n}\n\nexport class PtyManager {\n private child: IPty | null = null;\n private readonly provider: ProviderAdapter;\n private readonly providerArgs: string[];\n private readonly cwd: string;\n private readonly hook?: ProviderHookContext;\n private readonly tap: (data: string) => void;\n private readonly stdin: NodeJS.ReadStream;\n private readonly stdout: NodeJS.WriteStream;\n private readonly onSessionExit?: (code: number) => void;\n private readonly onResize?: (cols: number, rows: number) => void;\n\n constructor(options: PtyManagerOptions) {\n this.provider = options.provider;\n this.providerArgs = options.providerArgs;\n this.cwd = options.cwd;\n this.hook = options.hook;\n this.tap = options.tap;\n this.stdin = options.stdin;\n this.stdout = options.stdout;\n this.onSessionExit = options.onSessionExit;\n this.onResize = options.onResize;\n }\n\n start(): void {\n const { cols, rows } = readTtySize(this.stdout);\n\n const command = this.provider.buildTerminalCommand(\n { args: this.providerArgs, hook: this.hook },\n process.env,\n );\n const child = pty.spawn(command.command, command.args, {\n name: process.env.TERM ?? \"xterm-256color\",\n cols,\n rows,\n cwd: this.cwd,\n env: command.env as Record<string, string>,\n });\n this.child = child;\n\n // raw mode 仅在 stdin 为 TTY 时开启\n const isInteractive = this.stdin.isTTY === true;\n if (isInteractive) {\n this.stdin.setRawMode(true);\n }\n this.stdin.resume();\n\n // stdin -> PTY\n this.stdin.on(\"data\", (data: Buffer) => {\n child.write(data.toString());\n });\n\n // PTY -> stdout + tap\n child.onData((data: string) => this.handleData(data));\n\n // resize 防抖,50ms 窗口合并快速连续的尺寸变化\n let resizeTimer: ReturnType<typeof setTimeout> | null = null;\n this.stdout.on(\"resize\", () => {\n if (resizeTimer) clearTimeout(resizeTimer);\n resizeTimer = setTimeout(() => {\n const { cols: newCols, rows: newRows } = readTtySize(this.stdout);\n child.resize(newCols, newRows);\n this.onResize?.(newCols, newRows);\n }, 50);\n });\n\n // 子进程退出,按 Unix 惯例处理信号退出码,通过回调通知调用方\n child.onExit(({ exitCode, signal }) => {\n if (isInteractive) {\n try {\n this.stdin.setRawMode(false);\n } catch {\n // stdin 可能已关闭\n }\n }\n restoreHostTerminalModes(this.stdout);\n const code = signal ? 128 + signal : exitCode;\n this.onSessionExit?.(code);\n });\n\n // stdin 结束时写入 EOF 控制字符到 PTY\n this.stdin.on(\"end\", () => {\n child.write(\"\\x04\");\n });\n }\n\n /**\n * PTY 数据到达时的统一处理:OSC 9 修复 + 输出到终端 + 传给 tap\n */\n private handleData(data: string): void {\n // PTY 的 onlcr 会把 OSC 序列里的 \\n 转成 \\r\\n,还原为 \\n\n const fixed = data.replace(\n // eslint-disable-next-line no-control-regex\n /\\x1b\\]9;([\\s\\S]*?)\\x07/g,\n (_, content: string) => `\\x1b]9;${content.replace(/\\r\\n/g, \"\\n\")}\\x07`,\n );\n this.stdout.write(fixed);\n this.tap(data);\n }\n\n // 向 PTY 子进程写入数据,用于远程输入注入\n write(data: string): void {\n this.child?.write(data);\n }\n\n cleanup(exitCode: number): void {\n if (this.stdin.isTTY) {\n try {\n this.stdin.setRawMode(false);\n } catch {\n // stdin 可能已关闭\n }\n }\n restoreHostTerminalModes(this.stdout);\n if (this.child) {\n try {\n this.child.kill();\n } catch {\n // 子进程可能已退出\n }\n }\n this.onSessionExit?.(exitCode);\n }\n}\n","import { statSync } from \"node:fs\";\n\nfunction isDirectory(path: string | undefined): path is string {\n if (!path) return false;\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function resolveTerminalCwd(env: NodeJS.ProcessEnv = process.env): string {\n const candidates = [env.DEV_ANYWHERE_CWD, env.INIT_CWD, env.PWD, process.cwd()];\n return candidates.find(isDirectory) ?? process.cwd();\n}\n","import type { Socket } from \"node:net\";\nimport { createFSM } from \"../common/state-machine.js\";\nimport { serializeIpc } from \"../ipc/ipc-protocol.js\";\n\n// terminal 进程生命周期状态\nexport const TerminalState = {\n INIT: \"init\",\n CONNECTING_SERVICE: \"connecting_service\",\n CREATING_SESSION: \"creating_session\",\n RUNNING: \"running\",\n RECONNECTING: \"reconnecting\",\n EXITED: \"exited\",\n} as const;\nexport type TerminalState = (typeof TerminalState)[keyof typeof TerminalState];\n\n// 允许的状态转换。CREATING_SESSION/RUNNING 下可被 socket close 打断进入 RECONNECTING;\n// 任意非终态都可能被 PTY 退出或 SIGTERM 打断进入 EXITED。\nexport const TERMINAL_TRANSITIONS: Record<TerminalState, readonly TerminalState[]> = {\n init: [\"connecting_service\"],\n connecting_service: [\"creating_session\", \"exited\"],\n creating_session: [\"running\", \"reconnecting\", \"exited\"],\n running: [\"reconnecting\", \"exited\"],\n reconnecting: [\"creating_session\", \"running\", \"exited\"],\n exited: [],\n};\n\n// 下面三个依赖是 getter 而非值:因为它们在 terminal.ts 里是 let 变量,在 handler 创建之后还会变——\n// socket 在 reconnect 时被重新赋值为新实例,sessionId 在 session_create 成功后才有值,\n// idleCheckTimer 在 setupIdleCheck 跑完才赋值。直接传值只会记录 handler 构造那一刻的旧值。\ninterface ExitHandlerDeps {\n fsm: ReturnType<typeof createFSM<TerminalState>>;\n getSocket: () => Socket;\n getSessionId: () => string | null;\n getIdleCheckTimer: () => NodeJS.Timeout | null;\n // 测试注入点,production 默认 process.exit\n exit?: (code: number) => void;\n}\n\n// 构造统一的收尾函数:转 EXITED → 停 idle 定时器 → 给 serve 发 pty_deregister → 退进程。\n// onSessionExit 与 SIGTERM handler 共享同一实例;Ctrl+C 两连击或 PTY 退出与 SIGTERM 竞争时,\n// 第二次调用通过 fsm EXITED 检查直接短路。\nexport function createExitHandler(deps: ExitHandlerDeps): (code: number) => void {\n const exit = deps.exit ?? ((code: number) => process.exit(code));\n return (code: number) => {\n if (deps.fsm.is(TerminalState.EXITED)) return;\n deps.fsm.transitionTo(TerminalState.EXITED);\n const timer = deps.getIdleCheckTimer();\n if (timer) clearInterval(timer);\n const socket = deps.getSocket();\n const sessionId = deps.getSessionId();\n if (socket.writable && sessionId) {\n socket.end(serializeIpc({ type: \"pty_deregister\", sessionId }), () => exit(code));\n } else {\n exit(code);\n }\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-anywhere/proxy",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Transparent local proxy for AI coding CLIs that bridges local sessions to a web/PWA client via a relay server.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -52,8 +52,8 @@
52
52
  "@types/node": "^25.5.2",
53
53
  "@types/ws": "^8.18.1",
54
54
  "vitest": "^4.1.2",
55
- "@dev-anywhere/shared": "0.1.4",
56
- "@dev-anywhere/relay": "0.1.7"
55
+ "@dev-anywhere/shared": "0.1.8",
56
+ "@dev-anywhere/relay": "0.1.8"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsup",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/common/osc-extractor.ts","../src/common/pty-approval-state.ts","../src/common/state-machine.ts","../src/common/logger.ts"],"sourcesContent":["// OSC 0: 窗口标题 -- ESC ] 0 ; <title> BEL/ST\n// OSC 9: 通知 -- ESC ] 9 ; <text> BEL/ST\n// 每次调用创建新的 regex 实例避免 g flag 导致的 lastIndex 状态泄漏\n// eslint-disable-next-line no-control-regex\nconst OSC_PATTERN = /\\x1b\\](\\d+);([^\\x07\\x1b]*?)(?:\\x07|\\x1b\\\\)/g;\n\nexport type PtySemanticState = \"working\" | \"turn_complete\" | \"approval_wait\" | \"mid_pause\";\ntype PtySignalProvider = \"claude\" | \"codex\";\n\ninterface PtyStateEvent {\n state: PtySemanticState;\n title?: string;\n tool?: string;\n}\n\ninterface OscSequence {\n code: number;\n text: string;\n}\n\nexport function extractOscSequences(rawData: string): OscSequence[] {\n const regex = new RegExp(OSC_PATTERN.source, OSC_PATTERN.flags);\n const matches: OscSequence[] = [];\n\n let match: RegExpExecArray | null;\n while ((match = regex.exec(rawData)) !== null) {\n matches.push({ code: parseInt(match[1], 10), text: match[2] });\n }\n\n return matches;\n}\n\nfunction lastSequence(matches: OscSequence[], code: number): OscSequence | undefined {\n for (let i = matches.length - 1; i >= 0; i -= 1) {\n if (matches[i].code === code) return matches[i];\n }\n return undefined;\n}\n\nfunction isCodexActionRequiredTitle(title: string): boolean {\n return /\\bAction Required\\b/i.test(title);\n}\n\n// 从 PTY 原始数据中提取 OSC 语义信号。\n// OSC 9 优先级高于 OSC 0,无匹配时返回 null。\nexport function extractOscSignals(\n rawData: string,\n provider?: PtySignalProvider,\n): PtyStateEvent | null {\n const matches = extractOscSequences(rawData);\n\n if (matches.length === 0) return null;\n\n const osc0 = lastSequence(matches, 0);\n\n // OSC 9 优先级更高,包含具体的语义信号;同帧 OSC 0 仍保留 title 给 UI。\n const osc9 = lastSequence(matches, 9);\n if (osc9) {\n if (osc9.text.includes(\"waiting for your input\") || osc9.text.trim() === \"4;0;\") {\n return { state: \"turn_complete\", ...(osc0 ? { title: osc0.text } : {}) };\n }\n if (osc9.text.includes(\"needs your permission\")) {\n const toolMatch = osc9.text.match(/permission.*?:\\s*(\\S+)/);\n return {\n state: \"approval_wait\",\n ...(osc0 ? { title: osc0.text } : {}),\n ...(toolMatch?.[1] ? { tool: toolMatch[1] } : {}),\n };\n }\n }\n\n if (provider === \"codex\" && osc0 && isCodexActionRequiredTitle(osc0.text)) {\n return { state: \"approval_wait\", title: osc0.text };\n }\n\n // 仅有 OSC 0(标题/spinner 变化)时视为 MID_PAUSE\n if (osc0 && !osc9) {\n return { state: \"mid_pause\", title: osc0.text };\n }\n\n return null;\n}\n","import type { PtySemanticState } from \"./osc-extractor.js\";\n\nexport function shouldReleaseApprovalWait(options: {\n currentState: PtySemanticState;\n signalState?: PtySemanticState;\n}): boolean {\n if (options.currentState !== \"approval_wait\") return false;\n return options.signalState !== undefined && options.signalState !== \"approval_wait\";\n}\n\nexport function stateAfterApprovalRelease(signalState?: PtySemanticState): PtySemanticState {\n return signalState && signalState !== \"approval_wait\" ? signalState : \"working\";\n}\n","// 有限状态机 helper\n//\n// 提供显式转换表的小型 FSM,区分两类调用模式:\n// - transitionTo(throw):同步确定性流程里调用,非法转移代表 bug,立即暴露\n// - tryTransitionTo(bool):异步事件回调里调用,非法转移可能是吸收态残余事件,调用方按\n// isInAbsorbingState() 分级日志\n//\n// 吸收态采用传递闭包定义:终态(transitions=[])或所有出边都指向吸收态的状态都算吸收。\n// 这样 SessionState.ERROR (→[TERMINATED]) 和 RelayConnectionState.CLOSED ([]) 都被\n// 自动识别为吸收态,不用在 caller 里硬编码状态名。\n//\n// onTransition/onRejected 仅做日志/观测,不应 throw。\n\ninterface FSMDef<S extends string> {\n initial: S;\n // from-state → 允许转入的 to-state 列表;终态对应空数组\n transitions: Record<S, readonly S[]>;\n // 合法转换发生后触发,典型用法是结构化日志\n onTransition?: (from: S, to: S) => void;\n // tryTransitionTo 非法转移时触发;isAbsorbing 指示 from 是否吸收态(晚到残余 vs. 真非法)\n onRejected?: (from: S, to: S, isAbsorbing: boolean) => void;\n}\n\ninterface FSM<S extends string> {\n current(): S;\n is(state: S): boolean;\n isIn(states: readonly S[]): boolean;\n canTransitionTo(to: S): boolean;\n // 非法转换抛 Error;同步流程里当 assert 用\n transitionTo(to: S): void;\n // 非法转换返回 false,不抛;异步回调里用,配合 isInAbsorbingState 分级日志\n tryTransitionTo(to: S): boolean;\n // 当前是否在吸收态(传递闭包)\n isInAbsorbingState(): boolean;\n}\n\n// 计算吸收态集合:终态 + 所有出边都指向吸收态的状态,迭代至不动点\nfunction computeAbsorbingSet<S extends string>(transitions: Record<S, readonly S[]>): Set<S> {\n const absorbing = new Set<S>();\n const entries = Object.entries(transitions) as Array<[S, readonly S[]]>;\n for (const [s, outs] of entries) {\n if (outs.length === 0) absorbing.add(s);\n }\n let changed = true;\n while (changed) {\n changed = false;\n for (const [s, outs] of entries) {\n if (absorbing.has(s)) continue;\n if (outs.length > 0 && outs.every((t) => absorbing.has(t))) {\n absorbing.add(s);\n changed = true;\n }\n }\n }\n return absorbing;\n}\n\nexport function createFSM<S extends string>(def: FSMDef<S>): FSM<S> {\n let state = def.initial;\n const absorbing = computeAbsorbingSet(def.transitions);\n const tryTransitionTo = (to: S): boolean => {\n const allowed = def.transitions[state];\n if (!allowed?.includes(to)) {\n def.onRejected?.(state, to, absorbing.has(state));\n return false;\n }\n const from = state;\n state = to;\n def.onTransition?.(from, to);\n return true;\n };\n return {\n current: () => state,\n is: (s) => state === s,\n isIn: (ss) => ss.includes(state),\n canTransitionTo: (to) => def.transitions[state]?.includes(to) ?? false,\n transitionTo: (to) => {\n if (!tryTransitionTo(to)) {\n throw new Error(`Invalid FSM transition: ${state} -> ${to}`);\n }\n },\n tryTransitionTo,\n isInAbsorbingState: () => absorbing.has(state),\n };\n}\n\n// 无内部 state 的 FSM 视图,供 state 存在外部(如 SessionInfo、DB 行)的 per-instance 场景使用。\n// 调用方自行传入 from,canTransition 校验;吸收态判定通过 isAbsorbing(state) 提供。\ninterface StatelessFSM<S extends string> {\n canTransition(from: S, to: S): boolean;\n isAbsorbing(state: S): boolean;\n}\n\nexport function defineFSM<S extends string>(transitions: Record<S, readonly S[]>): StatelessFSM<S> {\n const absorbing = computeAbsorbingSet(transitions);\n return {\n canTransition: (from, to) => transitions[from]?.includes(to) ?? false,\n isAbsorbing: (state) => absorbing.has(state),\n };\n}\n","import { createLogger } from \"@dev-anywhere/shared\";\nimport { LOG_DIR } from \"./paths.js\";\n\nexport const serviceLogger = createLogger({\n name: \"service\",\n logDir: LOG_DIR,\n silent: !!process.env.VITEST,\n});\n\nexport const terminalLogger = createLogger({\n name: \"terminal\",\n level: \"debug\",\n logDir: LOG_DIR,\n silent: !!process.env.VITEST,\n});\n"],"mappings":"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","names":[]}