mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-20 15:25:50 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a5b263641 | ||
|
|
f2dd5142b3 | ||
|
|
4dcbaf1e66 | ||
|
|
0b304730d8 | ||
|
|
7a0dd3057e | ||
|
|
ca1c87f460 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-code-best",
|
"name": "claude-code-best",
|
||||||
"version": "1.9.2",
|
"version": "1.9.4",
|
||||||
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
|
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "claude-code-best <claude-code-best@proton.me>",
|
"author": "claude-code-best <claude-code-best@proton.me>",
|
||||||
|
|||||||
@@ -27,51 +27,49 @@ export function getMacroDefines(): Record<string, string> {
|
|||||||
* - scripts/dev.ts (bun run dev)
|
* - scripts/dev.ts (bun run dev)
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_BUILD_FEATURES = [
|
export const DEFAULT_BUILD_FEATURES = [
|
||||||
'BUDDY', 'TRANSCRIPT_CLASSIFIER', 'BRIDGE_MODE',
|
'BUDDY', // 陪伴宠物角色(Squirtle Waddles)
|
||||||
'AGENT_TRIGGERS_REMOTE',
|
'TRANSCRIPT_CLASSIFIER', // 对话分类器,用于标注会话类型
|
||||||
'CHICAGO_MCP',
|
'BRIDGE_MODE', // Remote Control / Bridge 模式,远程控制会话
|
||||||
'VOICE_MODE',
|
'AGENT_TRIGGERS_REMOTE', // Agent 触发远程会话连接
|
||||||
'SHOT_STATS',
|
'CHICAGO_MCP', // Chicago MCP 集成(内部代号)
|
||||||
'PROMPT_CACHE_BREAK_DETECTION',
|
'VOICE_MODE', // Push-to-Talk 语音输入模式
|
||||||
'TOKEN_BUDGET',
|
'SHOT_STATS', // 单次请求统计信息收集
|
||||||
|
'PROMPT_CACHE_BREAK_DETECTION', // 检测 prompt cache 是否被打破
|
||||||
|
'TOKEN_BUDGET', // Token 预算管理与控制
|
||||||
// P0: local features
|
// P0: local features
|
||||||
'AGENT_TRIGGERS',
|
'AGENT_TRIGGERS', // 本地 Agent 触发器(工具调用时启动子代理)
|
||||||
'ULTRATHINK',
|
'ULTRATHINK', // 超深度思考模式,增加推理链长度
|
||||||
'BUILTIN_EXPLORE_PLAN_AGENTS',
|
'BUILTIN_EXPLORE_PLAN_AGENTS', // 内置 Explore/Plan 子代理类型
|
||||||
'LODESTONE',
|
'LODESTONE', // 上下文锚点,优化长对话的相关性检索
|
||||||
// P1: API-dependent features
|
'EXTRACT_MEMORIES', // 自动从对话中提取并持久化记忆
|
||||||
'EXTRACT_MEMORIES',
|
'VERIFICATION_AGENT', // 验证代理,任务完成后自动校验结果
|
||||||
'VERIFICATION_AGENT',
|
'KAIROS_BRIEF', // Kairos 定时摘要(定时汇报当前状态)
|
||||||
'KAIROS_BRIEF',
|
'AWAY_SUMMARY', // 离线摘要(用户离开后生成总结)
|
||||||
'AWAY_SUMMARY',
|
'ULTRAPLAN', // 超级规划模式,深度分析后生成实施计划
|
||||||
'ULTRAPLAN',
|
// 'DAEMON', // 守护进程模式,长驻 supervisor 管理后台 worker(已禁用:内存占用过高)
|
||||||
// P2: daemon + remote control server
|
'ACP', // ACP 代理协议,支持外部 agent 接入
|
||||||
'DAEMON',
|
'WORKFLOW_SCRIPTS', // 工作流脚本(.claude/workflows/ 中的 YAML/MD)
|
||||||
// ACP (Agent Client Protocol) agent mode
|
'HISTORY_SNIP', // 历史消息裁剪,压缩上下文窗口
|
||||||
'ACP',
|
'CONTEXT_COLLAPSE', // 上下文折叠,自动压缩旧消息
|
||||||
// PR-package restored features
|
'MONITOR_TOOL', // Monitor 工具,流式监控后台进程输出
|
||||||
'WORKFLOW_SCRIPTS',
|
'FORK_SUBAGENT', // Fork 子代理,在隔离上下文中并行执行任务
|
||||||
'HISTORY_SNIP',
|
'UDS_INBOX', // Unix Domain Socket 收件箱,跨会话消息传递
|
||||||
'CONTEXT_COLLAPSE',
|
'KAIROS', // Kairos 定时任务系统核心
|
||||||
'MONITOR_TOOL',
|
'COORDINATOR_MODE', // 协调者模式,多代理团队任务调度
|
||||||
'FORK_SUBAGENT',
|
'LAN_PIPES', // 局域网管道,LAN 设备间通信
|
||||||
'UDS_INBOX',
|
'BG_SESSIONS', // 后台会话管理(ps/logs/attach/kill)
|
||||||
'KAIROS',
|
'TEMPLATES', // 模板任务(new/list/reply 子命令)
|
||||||
'COORDINATOR_MODE',
|
// 'REVIEW_ARTIFACT', // 代码审查产物(API 请求无响应,待排查 schema 兼容性)
|
||||||
'LAN_PIPES',
|
|
||||||
'BG_SESSIONS',
|
|
||||||
'TEMPLATES',
|
|
||||||
// 'REVIEW_ARTIFACT', // API 请求无响应,需进一步排查 schema 兼容性
|
|
||||||
// API content block types
|
// API content block types
|
||||||
'CONNECTOR_TEXT',
|
'CONNECTOR_TEXT', // Connector 文本块类型,扩展 API 内容格式
|
||||||
// Attribution tracking
|
// Attribution tracking
|
||||||
'COMMIT_ATTRIBUTION',
|
'COMMIT_ATTRIBUTION', // Git 提交归属追踪(记录 AI 辅助贡献)
|
||||||
// Server mode (claude server / claude open)
|
// Server mode (claude server / claude open)
|
||||||
'DIRECT_CONNECT',
|
'DIRECT_CONNECT', // 直连模式(claude server / claude open)
|
||||||
// Skill search
|
// Skill search
|
||||||
'EXPERIMENTAL_SKILL_SEARCH',
|
'EXPERIMENTAL_SKILL_SEARCH', // 实验性技能搜索(DiscoverSkills)
|
||||||
// P3: poor mode (disable extract_memories + prompt_suggestion)
|
// P3: poor mode
|
||||||
'POOR',
|
'POOR', // 穷鬼模式,跳过 extract_memories/prompt_suggestion 减少消耗
|
||||||
// Team Memory (shared memory files between agent teammates)
|
// Team Memory
|
||||||
'TEAMMEM',
|
'TEAMMEM', // 团队记忆,代理队友间共享记忆文件
|
||||||
]as const;
|
]as const;
|
||||||
|
|||||||
@@ -86,9 +86,13 @@ import {
|
|||||||
|
|
||||||
// Lazy: MessageSelector.tsx pulls React/ink; only needed for message filtering at query time
|
// Lazy: MessageSelector.tsx pulls React/ink; only needed for message filtering at query time
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const messageSelector =
|
const messageSelector = (): typeof import('src/components/MessageSelector.js') | null => {
|
||||||
(): typeof import('src/components/MessageSelector.js') =>
|
try {
|
||||||
require('src/components/MessageSelector.js')
|
return require('src/components/MessageSelector.js')
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
import {
|
import {
|
||||||
localCommandOutputToSDKAssistantMessage,
|
localCommandOutputToSDKAssistantMessage,
|
||||||
@@ -466,12 +470,13 @@ export class QueryEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter messages that should be acknowledged after transcript
|
// Filter messages that should be acknowledged after transcript
|
||||||
|
const _selector = messageSelector()
|
||||||
const replayableMessages = messagesFromUserInput.filter(
|
const replayableMessages = messagesFromUserInput.filter(
|
||||||
msg =>
|
msg =>
|
||||||
(msg.type === 'user' &&
|
(msg.type === 'user' &&
|
||||||
!msg.isMeta && // Skip synthetic caveat messages
|
!msg.isMeta && // Skip synthetic caveat messages
|
||||||
!msg.toolUseResult && // Skip tool results (they'll be acked from query)
|
!msg.toolUseResult && // Skip tool results (they'll be acked from query)
|
||||||
messageSelector().selectableUserMessagesFilter(msg)) || // Skip non-user-authored messages (task notifications, etc.)
|
(_selector?.selectableUserMessagesFilter(msg) ?? true)) || // Skip non-user-authored messages (task notifications, etc.)
|
||||||
(msg.type === 'system' && msg.subtype === 'compact_boundary'), // Always ack compact boundaries
|
(msg.type === 'system' && msg.subtype === 'compact_boundary'), // Always ack compact boundaries
|
||||||
)
|
)
|
||||||
const messagesToAck = replayUserMessages ? replayableMessages : []
|
const messagesToAck = replayUserMessages ? replayableMessages : []
|
||||||
@@ -643,8 +648,10 @@ export class QueryEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fileHistoryEnabled() && persistSession) {
|
if (fileHistoryEnabled() && persistSession) {
|
||||||
|
const _sel = messageSelector()
|
||||||
|
const _filter = _sel?.selectableUserMessagesFilter ?? ((_msg: unknown) => true)
|
||||||
messagesFromUserInput
|
messagesFromUserInput
|
||||||
.filter(messageSelector().selectableUserMessagesFilter)
|
.filter(_filter)
|
||||||
.forEach(message => {
|
.forEach(message => {
|
||||||
void fileHistoryMakeSnapshot(
|
void fileHistoryMakeSnapshot(
|
||||||
(updater: (prev: FileHistoryState) => FileHistoryState) => {
|
(updater: (prev: FileHistoryState) => FileHistoryState) => {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const bridge = feature('BRIDGE_MODE')
|
|||||||
? require('./commands/bridge/index.js').default
|
? require('./commands/bridge/index.js').default
|
||||||
: null
|
: null
|
||||||
const remoteControlServerCommand =
|
const remoteControlServerCommand =
|
||||||
feature('DAEMON') && feature('BRIDGE_MODE')
|
feature('BRIDGE_MODE')
|
||||||
? require('./commands/remoteControlServer/index.js').default
|
? require('./commands/remoteControlServer/index.js').default
|
||||||
: null
|
: null
|
||||||
const voiceCommand = feature('VOICE_MODE')
|
const voiceCommand = feature('VOICE_MODE')
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import { isBridgeEnabled } from '../../bridge/bridgeEnabled.js'
|
|||||||
import type { Command } from '../../commands.js'
|
import type { Command } from '../../commands.js'
|
||||||
|
|
||||||
function isEnabled(): boolean {
|
function isEnabled(): boolean {
|
||||||
if (!feature('DAEMON') || !feature('BRIDGE_MODE')) {
|
if (!feature('BRIDGE_MODE')) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if (feature('DAEMON')) {
|
||||||
|
return isBridgeEnabled()
|
||||||
|
}
|
||||||
|
// DAEMON feature disabled — still allow the command but warn at runtime
|
||||||
|
// that headless/daemon worker mode is unavailable.
|
||||||
return isBridgeEnabled()
|
return isBridgeEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -170,7 +170,12 @@ async function main(): Promise<void> {
|
|||||||
// perf-sensitive. No enableConfigs(), no analytics sinks at this layer —
|
// perf-sensitive. No enableConfigs(), no analytics sinks at this layer —
|
||||||
// workers are lean. If a worker kind needs configs/auth (assistant will),
|
// workers are lean. If a worker kind needs configs/auth (assistant will),
|
||||||
// it calls them inside its run() fn.
|
// it calls them inside its run() fn.
|
||||||
if (feature('DAEMON') && (args[0] === '--daemon-worker' || args[0]?.startsWith('--daemon-worker='))) {
|
if (args[0] === '--daemon-worker' || args[0]?.startsWith('--daemon-worker=')) {
|
||||||
|
if (!feature('DAEMON')) {
|
||||||
|
console.error('Error: --daemon-worker requires DAEMON feature to be enabled. Set FEATURE_DAEMON=1 or add DAEMON to DEFAULT_BUILD_FEATURES.')
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
const kind = args[0] === '--daemon-worker' ? args[1] : args[0].split('=')[1]
|
const kind = args[0] === '--daemon-worker' ? args[1] : args[0].split('=')[1]
|
||||||
const { runDaemonWorker } = await import('../daemon/workerRegistry.js')
|
const { runDaemonWorker } = await import('../daemon/workerRegistry.js')
|
||||||
await runDaemonWorker(kind)
|
await runDaemonWorker(kind)
|
||||||
|
|||||||
@@ -12,29 +12,19 @@
|
|||||||
*/
|
*/
|
||||||
import { feature } from 'bun:bundle'
|
import { feature } from 'bun:bundle'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import * as pt from '../utils/pipeTransport.js'
|
||||||
|
import * as pr from '../utils/pipeRegistry.js'
|
||||||
|
import * as mm from './useMasterMonitor.js'
|
||||||
|
import { getSessionId as _getSessionId } from '../bootstrap/state.js'
|
||||||
|
import * as lb from '../utils/lanBeacon.js'
|
||||||
|
import * as pp from '../utils/pipePermissionRelay.js'
|
||||||
|
import * as osm from 'os'
|
||||||
import type {
|
import type {
|
||||||
PipeMessage,
|
PipeMessage,
|
||||||
PipeServer,
|
PipeServer,
|
||||||
PipeIpcState,
|
PipeIpcState,
|
||||||
} from '../utils/pipeTransport.js'
|
} from '../utils/pipeTransport.js'
|
||||||
|
|
||||||
// Lazy-loaded module accessors (cached by Bun/Node require)
|
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
||||||
const pt = () =>
|
|
||||||
require('../utils/pipeTransport.js') as typeof import('../utils/pipeTransport.js')
|
|
||||||
const pr = () =>
|
|
||||||
require('../utils/pipeRegistry.js') as typeof import('../utils/pipeRegistry.js')
|
|
||||||
const mm = () =>
|
|
||||||
require('./useMasterMonitor.js') as typeof import('./useMasterMonitor.js')
|
|
||||||
const bs = () =>
|
|
||||||
require('../bootstrap/state.js') as typeof import('../bootstrap/state.js')
|
|
||||||
const lb = () =>
|
|
||||||
require('../utils/lanBeacon.js') as typeof import('../utils/lanBeacon.js')
|
|
||||||
const pp = () =>
|
|
||||||
require('../utils/pipePermissionRelay.js') as typeof import('../utils/pipePermissionRelay.js')
|
|
||||||
const osm = () => require('os') as typeof import('os')
|
|
||||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Types
|
// Types
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -54,9 +44,9 @@ export type UsePipeIpcOptions = {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function removeDeadSlave(slaveName: string, store: StoreApi): void {
|
function removeDeadSlave(slaveName: string, store: StoreApi): void {
|
||||||
mm().removeSlaveClient(slaveName)
|
mm.removeSlaveClient(slaveName)
|
||||||
store.setState((prev: any) => {
|
store.setState((prev: any) => {
|
||||||
const pipeIpc = pt().getPipeIpc(prev)
|
const pipeIpc = pt.getPipeIpc(prev)
|
||||||
const { [slaveName]: _removed, ...remainingSlaves } = pipeIpc.slaves
|
const { [slaveName]: _removed, ...remainingSlaves } = pipeIpc.slaves
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
@@ -108,7 +98,7 @@ function refreshDiscoveredPipes(
|
|||||||
// Include LAN beacon peers so they aren't wiped out by heartbeat
|
// Include LAN beacon peers so they aren't wiped out by heartbeat
|
||||||
let lanDiscovered: typeof freshDiscovered = []
|
let lanDiscovered: typeof freshDiscovered = []
|
||||||
if (feature('LAN_PIPES')) {
|
if (feature('LAN_PIPES')) {
|
||||||
const beacon = lb().getLanBeacon()
|
const beacon = lb.getLanBeacon()
|
||||||
if (beacon) {
|
if (beacon) {
|
||||||
const localNames = new Set(freshDiscovered.map(p => p.pipeName))
|
const localNames = new Set(freshDiscovered.map(p => p.pipeName))
|
||||||
localNames.add(pipeName)
|
localNames.add(pipeName)
|
||||||
@@ -131,7 +121,7 @@ function refreshDiscoveredPipes(
|
|||||||
const allDiscovered = [...freshDiscovered, ...lanDiscovered]
|
const allDiscovered = [...freshDiscovered, ...lanDiscovered]
|
||||||
|
|
||||||
// Only update state if the list actually changed
|
// Only update state if the list actually changed
|
||||||
const prev = pt().getPipeIpc(store.getState())
|
const prev = pt.getPipeIpc(store.getState())
|
||||||
const prevNames = (prev.discoveredPipes ?? [])
|
const prevNames = (prev.discoveredPipes ?? [])
|
||||||
.map((p: any) => p.pipeName)
|
.map((p: any) => p.pipeName)
|
||||||
.join(',')
|
.join(',')
|
||||||
@@ -139,7 +129,7 @@ function refreshDiscoveredPipes(
|
|||||||
if (prevNames === newNames) return
|
if (prevNames === newNames) return
|
||||||
|
|
||||||
store.setState((prev: any) => {
|
store.setState((prev: any) => {
|
||||||
const pipeIpc = pt().getPipeIpc(prev)
|
const pipeIpc = pt.getPipeIpc(prev)
|
||||||
const aliveNames = new Set(allDiscovered.map(pipe => pipe.pipeName))
|
const aliveNames = new Set(allDiscovered.map(pipe => pipe.pipeName))
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
@@ -174,8 +164,8 @@ function registerMessageHandlers(
|
|||||||
server.onMessage((msg: PipeMessage, reply) => {
|
server.onMessage((msg: PipeMessage, reply) => {
|
||||||
if (msg.type !== 'attach_request') return
|
if (msg.type !== 'attach_request') return
|
||||||
const state = store.getState()
|
const state = store.getState()
|
||||||
const currentPipeState = pt().getPipeIpc(state)
|
const currentPipeState = pt.getPipeIpc(state)
|
||||||
if (pt().isPipeControlled(currentPipeState)) {
|
if (pt.isPipeControlled(currentPipeState)) {
|
||||||
reply({ type: 'attach_reject', data: 'Already controlled' })
|
reply({ type: 'attach_reject', data: 'Already controlled' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -192,7 +182,7 @@ function registerMessageHandlers(
|
|||||||
|
|
||||||
const clients = Array.from((server as any).clients as Set<any>)
|
const clients = Array.from((server as any).clients as Set<any>)
|
||||||
const masterSocket = clients[clients.length - 1]
|
const masterSocket = clients[clients.length - 1]
|
||||||
pp().setPipeRelay((relayMsg: any) => {
|
pp.setPipeRelay((relayMsg: any) => {
|
||||||
if (masterSocket && !masterSocket.destroyed) {
|
if (masterSocket && !masterSocket.destroyed) {
|
||||||
relayMsg.from = relayMsg.from ?? pipeName
|
relayMsg.from = relayMsg.from ?? pipeName
|
||||||
relayMsg.ts = relayMsg.ts ?? new Date().toISOString()
|
relayMsg.ts = relayMsg.ts ?? new Date().toISOString()
|
||||||
@@ -203,9 +193,9 @@ function registerMessageHandlers(
|
|||||||
store.setState((prev: any) => ({
|
store.setState((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
pipeIpc: {
|
pipeIpc: {
|
||||||
...pt().getPipeIpc(prev),
|
...pt.getPipeIpc(prev),
|
||||||
role: 'sub',
|
role: 'sub',
|
||||||
displayRole: pt().getPipeDisplayRole(pt().getPipeIpc(prev)),
|
displayRole: pt.getPipeDisplayRole(pt.getPipeIpc(prev)),
|
||||||
attachedBy: msg.from ?? 'unknown',
|
attachedBy: msg.from ?? 'unknown',
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
@@ -230,8 +220,7 @@ function registerMessageHandlers(
|
|||||||
server.onMessage((msg: PipeMessage, _reply) => {
|
server.onMessage((msg: PipeMessage, _reply) => {
|
||||||
if (msg.type !== 'permission_response' && msg.type !== 'permission_cancel')
|
if (msg.type !== 'permission_response' && msg.type !== 'permission_cancel')
|
||||||
return
|
return
|
||||||
const { resolvePipePermissionResponse, cancelPipePermissionRequest } =
|
const { resolvePipePermissionResponse, cancelPipePermissionRequest } = pp
|
||||||
require('../utils/pipePermissionRelay.js') as typeof import('../utils/pipePermissionRelay.js')
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = msg.data ? JSON.parse(msg.data) : undefined
|
const payload = msg.data ? JSON.parse(msg.data) : undefined
|
||||||
@@ -249,28 +238,27 @@ function registerMessageHandlers(
|
|||||||
// Handle relay mute/unmute from master
|
// Handle relay mute/unmute from master
|
||||||
server.onMessage((msg: PipeMessage, _reply) => {
|
server.onMessage((msg: PipeMessage, _reply) => {
|
||||||
if (msg.type === 'relay_mute') {
|
if (msg.type === 'relay_mute') {
|
||||||
pp().setRelayMuted(true)
|
pp.setRelayMuted(true)
|
||||||
} else if (msg.type === 'relay_unmute') {
|
} else if (msg.type === 'relay_unmute') {
|
||||||
pp().setRelayMuted(false)
|
pp.setRelayMuted(false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle detach
|
// Handle detach
|
||||||
server.onMessage((msg: PipeMessage, _reply) => {
|
server.onMessage((msg: PipeMessage, _reply) => {
|
||||||
if (msg.type !== 'detach') return
|
if (msg.type !== 'detach') return
|
||||||
const { clearPendingPipePermissions } =
|
const { clearPendingPipePermissions } = pp
|
||||||
require('../utils/pipePermissionRelay.js') as typeof import('../utils/pipePermissionRelay.js')
|
|
||||||
clearPendingPipePermissions('Pipe detached before permission was resolved.')
|
clearPendingPipePermissions('Pipe detached before permission was resolved.')
|
||||||
pp().setPipeRelay(null)
|
pp.setPipeRelay(null)
|
||||||
store.setState((prev: any) => ({
|
store.setState((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
pipeIpc: (() => {
|
pipeIpc: (() => {
|
||||||
const pipeIpc = pt().getPipeIpc(prev)
|
const pipeIpc = pt.getPipeIpc(prev)
|
||||||
const nextRole = pipeIpc.subIndex != null ? 'sub' : 'main'
|
const nextRole = pipeIpc.subIndex != null ? 'sub' : 'main'
|
||||||
const nextPipeState = { ...pipeIpc, role: nextRole, attachedBy: null }
|
const nextPipeState = { ...pipeIpc, role: nextRole, attachedBy: null }
|
||||||
return {
|
return {
|
||||||
...nextPipeState,
|
...nextPipeState,
|
||||||
displayRole: pt().getPipeDisplayRole(nextPipeState as PipeIpcState),
|
displayRole: pt.getPipeDisplayRole(nextPipeState as PipeIpcState),
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
}))
|
}))
|
||||||
@@ -289,11 +277,11 @@ function runMainHeartbeat(
|
|||||||
): void {
|
): void {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
await pr().cleanupStaleEntries()
|
await pr.cleanupStaleEntries()
|
||||||
const aliveSubs = await pr().getAliveSubs()
|
const aliveSubs = await pr.getAliveSubs()
|
||||||
refreshDiscoveredPipes(pipeName, aliveSubs, store)
|
refreshDiscoveredPipes(pipeName, aliveSubs, store)
|
||||||
|
|
||||||
const connectedSlaves = mm().getAllSlaveClients()
|
const connectedSlaves = mm.getAllSlaveClients()
|
||||||
const aliveSubNames = new Set(aliveSubs.map(sub => sub.pipeName))
|
const aliveSubNames = new Set(aliveSubs.map(sub => sub.pipeName))
|
||||||
|
|
||||||
// Build unified attach target list: local subs + LAN peers
|
// Build unified attach target list: local subs + LAN peers
|
||||||
@@ -307,7 +295,7 @@ function runMainHeartbeat(
|
|||||||
|
|
||||||
// Add LAN peers as attach targets
|
// Add LAN peers as attach targets
|
||||||
if (feature('LAN_PIPES')) {
|
if (feature('LAN_PIPES')) {
|
||||||
const beacon = lb().getLanBeacon()
|
const beacon = lb.getLanBeacon()
|
||||||
if (beacon) {
|
if (beacon) {
|
||||||
const localNames = new Set(attachTargets.map(t => t.pipeName))
|
const localNames = new Set(attachTargets.map(t => t.pipeName))
|
||||||
localNames.add(pipeName)
|
localNames.add(pipeName)
|
||||||
@@ -323,7 +311,7 @@ function runMainHeartbeat(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentPipeState = pt().getPipeIpc(store.getState())
|
const currentPipeState = pt.getPipeIpc(store.getState())
|
||||||
|
|
||||||
for (const target of attachTargets) {
|
for (const target of attachTargets) {
|
||||||
if (target.pipeName === pipeName) continue
|
if (target.pipeName === pipeName) continue
|
||||||
@@ -331,7 +319,7 @@ function runMainHeartbeat(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const myName = currentPipeState.serverName ?? pipeName
|
const myName = currentPipeState.serverName ?? pipeName
|
||||||
const client = await pt().connectToPipe(
|
const client = await pt.connectToPipe(
|
||||||
target.pipeName,
|
target.pipeName,
|
||||||
myName,
|
myName,
|
||||||
3000,
|
3000,
|
||||||
@@ -362,7 +350,7 @@ function runMainHeartbeat(
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (attached && !disposed.current) {
|
if (attached && !disposed.current) {
|
||||||
mm().addSlaveClient(target.pipeName, client)
|
mm.addSlaveClient(target.pipeName, client)
|
||||||
|
|
||||||
client.on('disconnect', () => {
|
client.on('disconnect', () => {
|
||||||
removeDeadSlave(target.pipeName, store)
|
removeDeadSlave(target.pipeName, store)
|
||||||
@@ -371,11 +359,11 @@ function runMainHeartbeat(
|
|||||||
store.setState((prev: any) => ({
|
store.setState((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
pipeIpc: {
|
pipeIpc: {
|
||||||
...pt().getPipeIpc(prev),
|
...pt.getPipeIpc(prev),
|
||||||
role: 'master',
|
role: 'master',
|
||||||
displayRole: 'master',
|
displayRole: 'master',
|
||||||
slaves: {
|
slaves: {
|
||||||
...pt().getPipeIpc(prev).slaves,
|
...pt.getPipeIpc(prev).slaves,
|
||||||
[target.pipeName]: {
|
[target.pipeName]: {
|
||||||
name: target.pipeName,
|
name: target.pipeName,
|
||||||
connectedAt: new Date().toISOString(),
|
connectedAt: new Date().toISOString(),
|
||||||
@@ -395,7 +383,7 @@ function runMainHeartbeat(
|
|||||||
// Clean up slaves that are no longer alive
|
// Clean up slaves that are no longer alive
|
||||||
let lanPeerNames: Set<string> | null = null
|
let lanPeerNames: Set<string> | null = null
|
||||||
if (feature('LAN_PIPES')) {
|
if (feature('LAN_PIPES')) {
|
||||||
const beacon = lb().getLanBeacon()
|
const beacon = lb.getLanBeacon()
|
||||||
if (beacon) {
|
if (beacon) {
|
||||||
lanPeerNames = new Set(beacon.getPeers().keys())
|
lanPeerNames = new Set(beacon.getPeers().keys())
|
||||||
}
|
}
|
||||||
@@ -422,28 +410,28 @@ function runSubHeartbeat(
|
|||||||
): void {
|
): void {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
const mainAlive = await pr().isMainAlive()
|
const mainAlive = await pr.isMainAlive()
|
||||||
if (!mainAlive && !disposed.current) {
|
if (!mainAlive && !disposed.current) {
|
||||||
const registry = await pr().readRegistry()
|
const registry = await pr.readRegistry()
|
||||||
const isSameMachine = pr().isMainMachine(machineId, registry)
|
const isSameMachine = pr.isMainMachine(machineId, registry)
|
||||||
|
|
||||||
if (isSameMachine) {
|
if (isSameMachine) {
|
||||||
await pr().registerAsMain(entry)
|
await pr.registerAsMain(entry)
|
||||||
} else {
|
} else {
|
||||||
await pr().revertToIndependent(pipeName)
|
await pr.revertToIndependent(pipeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
store.setState((prev: any) => ({
|
store.setState((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
pipeIpc: {
|
pipeIpc: {
|
||||||
...pt().getPipeIpc(prev),
|
...pt.getPipeIpc(prev),
|
||||||
role: 'main',
|
role: 'main',
|
||||||
subIndex: null,
|
subIndex: null,
|
||||||
displayRole: 'main',
|
displayRole: 'main',
|
||||||
attachedBy: null,
|
attachedBy: null,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
pp().setPipeRelay(null)
|
pp.setPipeRelay(null)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Heartbeat check error — non-fatal
|
// Heartbeat check error — non-fatal
|
||||||
@@ -462,7 +450,9 @@ export function usePipeIpc({
|
|||||||
if (!feature('UDS_INBOX')) return
|
if (!feature('UDS_INBOX')) return
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pipeName = `cli-${bs().getSessionId().slice(0, 8)}`
|
const sessionId = _getSessionId()
|
||||||
|
if (!sessionId) return
|
||||||
|
const pipeName = `cli-${sessionId.slice(0, 8)}`
|
||||||
const disposed = { current: false }
|
const disposed = { current: false }
|
||||||
let heartbeatTimer: ReturnType<typeof setInterval> | null = null
|
let heartbeatTimer: ReturnType<typeof setInterval> | null = null
|
||||||
let heartbeatBusy = false
|
let heartbeatBusy = false
|
||||||
@@ -471,11 +461,11 @@ export function usePipeIpc({
|
|||||||
void (async () => {
|
void (async () => {
|
||||||
try {
|
try {
|
||||||
// --- Phase 1: Role determination ---
|
// --- Phase 1: Role determination ---
|
||||||
const machId = await pr().getMachineId()
|
const machId = await pr.getMachineId()
|
||||||
const mac = pr().getMacAddress()
|
const mac = pr.getMacAddress()
|
||||||
const localIp = pt().getLocalIp()
|
const localIp = pt.getLocalIp()
|
||||||
const host = osm().hostname()
|
const host = osm.hostname()
|
||||||
const roleResult = await pr().determineRole(machId)
|
const roleResult = await pr.determineRole(machId)
|
||||||
|
|
||||||
const entry = {
|
const entry = {
|
||||||
id: pipeName,
|
id: pipeName,
|
||||||
@@ -493,29 +483,29 @@ export function usePipeIpc({
|
|||||||
let displayRole = 'main'
|
let displayRole = 'main'
|
||||||
|
|
||||||
if (roleResult.role === 'main' || roleResult.role === 'main-recover') {
|
if (roleResult.role === 'main' || roleResult.role === 'main-recover') {
|
||||||
await pr().registerAsMain(entry)
|
await pr.registerAsMain(entry)
|
||||||
} else {
|
} else {
|
||||||
subIndex = roleResult.subIndex
|
subIndex = roleResult.subIndex
|
||||||
await pr().registerAsSub(entry, subIndex)
|
await pr.registerAsSub(entry, subIndex)
|
||||||
initialRole = 'sub'
|
initialRole = 'sub'
|
||||||
displayRole = `sub-${subIndex}`
|
displayRole = `sub-${subIndex}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Phase 2: Server creation ---
|
// --- Phase 2: Server creation ---
|
||||||
const server = await pt().createPipeServer(
|
const server = await pt.createPipeServer(
|
||||||
pipeName,
|
pipeName,
|
||||||
feature('LAN_PIPES') ? { enableTcp: true, tcpPort: 0 } : undefined,
|
feature('LAN_PIPES') ? { enableTcp: true, tcpPort: 0 } : undefined,
|
||||||
)
|
)
|
||||||
pipeServer = server
|
pipeServer = server
|
||||||
if (disposed.current) {
|
if (disposed.current) {
|
||||||
await server.close()
|
await server.close()
|
||||||
await pr().unregister(pipeName)
|
await pr.unregister(pipeName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Phase 3: LAN beacon ---
|
// --- Phase 3: LAN beacon ---
|
||||||
if (feature('LAN_PIPES') && server.tcpAddress) {
|
if (feature('LAN_PIPES') && server.tcpAddress) {
|
||||||
const beacon = new (lb().LanBeacon)({
|
const beacon = new (lb.LanBeacon)({
|
||||||
pipeName,
|
pipeName,
|
||||||
machineId: machId,
|
machineId: machId,
|
||||||
hostname: host,
|
hostname: host,
|
||||||
@@ -524,7 +514,7 @@ export function usePipeIpc({
|
|||||||
role: initialRole,
|
role: initialRole,
|
||||||
})
|
})
|
||||||
beacon.start()
|
beacon.start()
|
||||||
lb().setLanBeacon(beacon)
|
lb.setLanBeacon(beacon)
|
||||||
|
|
||||||
const entryWithTcp = {
|
const entryWithTcp = {
|
||||||
...entry,
|
...entry,
|
||||||
@@ -532,9 +522,9 @@ export function usePipeIpc({
|
|||||||
lanVisible: true,
|
lanVisible: true,
|
||||||
}
|
}
|
||||||
if (initialRole === 'main') {
|
if (initialRole === 'main') {
|
||||||
await pr().registerAsMain(entryWithTcp)
|
await pr.registerAsMain(entryWithTcp)
|
||||||
} else if (subIndex != null) {
|
} else if (subIndex != null) {
|
||||||
await pr().registerAsSub(entryWithTcp, subIndex)
|
await pr.registerAsSub(entryWithTcp, subIndex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,7 +532,7 @@ export function usePipeIpc({
|
|||||||
store.setState((prev: any) => ({
|
store.setState((prev: any) => ({
|
||||||
...prev,
|
...prev,
|
||||||
pipeIpc: {
|
pipeIpc: {
|
||||||
...pt().getPipeIpc(prev),
|
...pt.getPipeIpc(prev),
|
||||||
serverName: pipeName,
|
serverName: pipeName,
|
||||||
role: initialRole,
|
role: initialRole,
|
||||||
subIndex,
|
subIndex,
|
||||||
@@ -570,7 +560,7 @@ export function usePipeIpc({
|
|||||||
if (disposed.current || heartbeatBusy) return
|
if (disposed.current || heartbeatBusy) return
|
||||||
heartbeatBusy = true
|
heartbeatBusy = true
|
||||||
|
|
||||||
const currentPipeState = pt().getPipeIpc(store.getState())
|
const currentPipeState = pt.getPipeIpc(store.getState())
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentPipeState.role === 'main' ||
|
currentPipeState.role === 'main' ||
|
||||||
@@ -600,7 +590,7 @@ export function usePipeIpc({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send detach to all slaves
|
// Send detach to all slaves
|
||||||
const allClients = mm().getAllSlaveClients()
|
const allClients = mm.getAllSlaveClients()
|
||||||
for (const [name, client] of allClients.entries()) {
|
for (const [name, client] of allClients.entries()) {
|
||||||
try {
|
try {
|
||||||
client.send({ type: 'detach' })
|
client.send({ type: 'detach' })
|
||||||
@@ -610,23 +600,21 @@ export function usePipeIpc({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop LAN beacon
|
// Stop LAN beacon
|
||||||
const beacon = lb().getLanBeacon()
|
const beacon = lb.getLanBeacon()
|
||||||
if (beacon) {
|
if (beacon) {
|
||||||
try {
|
try {
|
||||||
beacon.stop()
|
beacon.stop()
|
||||||
} catch {}
|
} catch {}
|
||||||
lb().setLanBeacon(null)
|
lb.setLanBeacon(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unregister + close server
|
// Unregister + close server
|
||||||
void pr()
|
pr.unregister(pipeName).catch(() => {})
|
||||||
.unregister(pipeName)
|
|
||||||
.catch(() => {})
|
|
||||||
if (pipeServer) {
|
if (pipeServer) {
|
||||||
void pipeServer.close().catch(() => {})
|
void pipeServer.close().catch(() => {})
|
||||||
pipeServer = null
|
pipeServer = null
|
||||||
}
|
}
|
||||||
pp().setPipeRelay(null)
|
pp.setPipeRelay(null)
|
||||||
}
|
}
|
||||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user