mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
remove transcript timeout for codex
This commit is contained in:
@@ -229,8 +229,12 @@ describe('codexLocalLauncher', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('warns on session match failure without aborting local Codex launch', async () => {
|
||||
it('does not emit a session warning while waiting for the first transcript path', async () => {
|
||||
const { session, sessionEvents, getLocalLaunchFailure } = createSessionStub('default', undefined, 'c:\\workspace\\project');
|
||||
let releaseRunBarrier: (() => void) | undefined;
|
||||
harness.runBarrier = new Promise((resolve) => {
|
||||
releaseRunBarrier = resolve;
|
||||
});
|
||||
|
||||
vi.useFakeTimers();
|
||||
const launcherPromise = codexLocalLauncher(session as never);
|
||||
@@ -239,19 +243,25 @@ describe('codexLocalLauncher', () => {
|
||||
await Promise.resolve();
|
||||
vi.advanceTimersByTime(10_000);
|
||||
await Promise.resolve();
|
||||
expect(sessionEvents).toEqual([]);
|
||||
|
||||
if (releaseRunBarrier) {
|
||||
releaseRunBarrier();
|
||||
}
|
||||
await launcherPromise;
|
||||
|
||||
expect(harness.launches.length).toBeGreaterThan(0);
|
||||
expect(getLocalLaunchFailure()).toBeNull();
|
||||
expect(sessionEvents).toContainEqual({
|
||||
type: 'message',
|
||||
message: 'No Codex SessionStart hook transcript path received within 10000ms. Keeping local Codex running; remote transcript sync may be unavailable for this launch.'
|
||||
});
|
||||
expect(sessionEvents).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not reuse a stale transcript path from a previous launch', async () => {
|
||||
const staleTranscriptPath = join(tempDir, 'stale-transcript.jsonl');
|
||||
const { session, sessionEvents } = createSessionStub('default', undefined, '/tmp/worktree', staleTranscriptPath);
|
||||
let releaseRunBarrier: (() => void) | undefined;
|
||||
harness.runBarrier = new Promise((resolve) => {
|
||||
releaseRunBarrier = resolve;
|
||||
});
|
||||
|
||||
vi.useFakeTimers();
|
||||
const launcherPromise = codexLocalLauncher(session as never);
|
||||
@@ -261,12 +271,14 @@ describe('codexLocalLauncher', () => {
|
||||
|
||||
vi.advanceTimersByTime(10_000);
|
||||
await Promise.resolve();
|
||||
expect(sessionEvents).toEqual([]);
|
||||
|
||||
if (releaseRunBarrier) {
|
||||
releaseRunBarrier();
|
||||
}
|
||||
await launcherPromise;
|
||||
|
||||
expect(sessionEvents).toContainEqual({
|
||||
type: 'message',
|
||||
message: 'No Codex SessionStart hook transcript path received within 10000ms. Keeping local Codex running; remote transcript sync may be unavailable for this launch.'
|
||||
});
|
||||
expect(sessionEvents).toEqual([]);
|
||||
});
|
||||
|
||||
it('passes SessionStart hook config into local Codex launch', async () => {
|
||||
|
||||
@@ -10,8 +10,6 @@ import { stripCodexCliOverrides } from './utils/codexCliOverrides';
|
||||
import { buildCodexPermissionModeCliArgs } from './utils/permissionModeConfig';
|
||||
import { BaseLocalLauncher } from '@/modules/common/launcher/BaseLocalLauncher';
|
||||
|
||||
const SESSION_HOOK_TIMEOUT_MS = 10_000;
|
||||
|
||||
export async function codexLocalLauncher(session: CodexSession): Promise<'switch' | 'exit'> {
|
||||
const resumeSessionId = session.sessionId;
|
||||
let scanner: CodexSessionScanner | null = null;
|
||||
@@ -47,23 +45,6 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
|
||||
});
|
||||
logger.debug(`[codex-local]: Started Codex SessionStart hook server on port ${hookServer.port}`);
|
||||
|
||||
let hookTimeout: ReturnType<typeof setTimeout> | null = setTimeout(() => {
|
||||
hookTimeout = null;
|
||||
const message = `No Codex SessionStart hook transcript path received within ${SESSION_HOOK_TIMEOUT_MS}ms.`;
|
||||
logger.warn(`[codex-local]: ${message}`);
|
||||
session.sendSessionEvent({
|
||||
type: 'message',
|
||||
message: `${message} Keeping local Codex running; remote transcript sync may be unavailable for this launch.`
|
||||
});
|
||||
}, SESSION_HOOK_TIMEOUT_MS);
|
||||
|
||||
const clearHookTimeout = () => {
|
||||
if (hookTimeout) {
|
||||
clearTimeout(hookTimeout);
|
||||
hookTimeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
const reportTranscriptSyncFailure = (transcriptPath: string, error: unknown): void => {
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
const message = `Codex transcript sync failed for ${transcriptPath}: ${detail}`;
|
||||
@@ -80,7 +61,6 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
|
||||
|
||||
const processTranscriptPath = async (transcriptPath: string): Promise<void> => {
|
||||
hookReady = true;
|
||||
clearHookTimeout();
|
||||
if (shuttingDown) {
|
||||
return;
|
||||
}
|
||||
@@ -170,7 +150,6 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
|
||||
return await launcher.run();
|
||||
} finally {
|
||||
shuttingDown = true;
|
||||
clearHookTimeout();
|
||||
session.removeTranscriptPathCallback(handleTranscriptPathCallback);
|
||||
hookServer.stop();
|
||||
if (pendingScannerSetup) {
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# HAPI 重构计划
|
||||
|
||||
## 摘要
|
||||
- 基线: `bun run typecheck`、`bun run test` 全绿;当前重点=结构清理,不是修红。
|
||||
- 说明: 你提到的 `server/`,仓库实际对应 `hub/`;以下按 `cli/`、`hub/`、`web/`、`shared/` 审查。
|
||||
- 静态发现: `cli` 3 组循环依赖,`hub` 1 组,`web` 1 组;存在确认未引用文件、兼容别名、重复 DTO、超大模块。
|
||||
|
||||
## 高优先级
|
||||
### [高] 统一跨端协议/DTO/Schema,收口到 `shared/`
|
||||
位置: `shared/src/*`, `web/src/types/api.ts`, `cli/src/api/types.ts`, `hub/src/web/routes/*.ts`, `hub/src/sync/*`
|
||||
问题: HTTP/SSE/RPC 响应类型分散本地定义;`Machine`/`RunnerState`/Session 响应体多处重复;事件 patch 语义靠前端猜测,易漂移。
|
||||
方案: 把 Web API DTO、SSE patch/full event、CLI route response、Machine/RunnerState schema 全部提升到 `shared/`;`hub` 只产出 shared schema,`cli/web` 只消费 shared 类型;删除本地镜像类型。
|
||||
影响范围: `cli` API 客户端、`hub` 路由/SSE、`web` 查询层与实时层、测试夹具。
|
||||
|
||||
### [高] 清除兼容包袱与已确认死代码
|
||||
位置: `cli/src/commands/registry.ts`, `cli/src/commands/{connect,notify}.ts`, `cli/src/persistence.ts`, `cli/src/ui/apiUrlInit.ts`, `cli/src/utils/autoStartServer.ts`, `hub/src/config/{settings.ts,serverSettings.ts}`, `hub/src/store/index.ts`, `shared/src/modes.ts`, `web/src/components/{MachineList.tsx,SpawnSession.tsx}`, `web/src/hooks/useScrollToBottom.ts`, `web/src/lib/locales/{en.ts,zh-CN.ts}`, `cli/src/api/socketOutbox.ts`, `cli/src/agent/runners/gemini.ts`
|
||||
问题: 还保留 `hapi server` alias、`serverUrl/webapp*` 旧字段、运行时 DB migration、`connect/notify` 直连占位命令、旧新建会话组件、未使用 outbox/Gemini 注册器、旧文案键;噪音大,误导后续重构。
|
||||
方案: 直接删兼容读取/迁移/别名/stub;只保留当前主路径 `hub`、`apiUrl`、`listen*`、`publicUrl`;DB 不再运行历史迁移,改“版本不符即重建/离线迁移”;删除未引用 UI/命令/工具与旧 locale key。
|
||||
影响范围: CLI 帮助与启动流程、Hub 配置加载、SQLite 初始化、Web bundle、文档。
|
||||
|
||||
### [高] 拆分 `hub` 会话核心,消除缓存/发布/传输耦合
|
||||
位置: `hub/src/sync/{syncEngine.ts,sessionCache.ts,machineCache.ts,messageService.ts,eventPublisher.ts}`, `hub/src/sse/sseManager.ts`
|
||||
问题: `SessionCache` 同时做 hydration、schema parse、todo backfill、keepalive、config 持久化、merge/dedup/delete;`SyncEngine` 再包一层调度;`SSEManager` 反向 import `syncEngine` 类型,形成循环依赖。
|
||||
方案: 切成 `sessionRepository`、`sessionLivenessService`、`sessionConfigService`、`sessionMergeService`、`machineLivenessService`、`eventBus`;新增 `hub/src/sync/types.ts`,SSE 仅依赖 shared event types;缓存层只管内存状态,不再承载业务修复逻辑。
|
||||
影响范围: Hub SSE、Socket handler、REST route、session dedup/merge、相关测试。
|
||||
|
||||
### [高] 抽象 CLI 多代理运行时模板,去掉平行实现
|
||||
位置: `cli/src/{claude,codex,cursor,opencode}/{loop.ts,session.ts,*LocalLauncher.ts,*RemoteLauncher.ts}`, `cli/src/agent/{sessionBase.ts,loopBase.ts}`
|
||||
问题: 四套 agent 会话/loop/local/remote 结构高度相似;setter、failure 记录、client 代理、mode 切换、session 构造重复;`loop -> session -> launcher` 互相引用,形成循环依赖。
|
||||
方案: 建统一 agent runtime kit: `SessionContext`、`LocalAdapter`、`RemoteAdapter`、`ModeConfig`、`LaunchPolicy`;把每个 agent 的差异收敛到“参数翻译 + 事件转换 + 特有 hook/scanner”;把 mode type 从 `loop.ts` 抽到独立 `modes.ts`,打断循环。
|
||||
影响范围: `claude/codex/cursor/opencode` 主流程、runner spawn、权限模式、远程切换、单测。
|
||||
|
||||
### [高] 规范 `hub` 路由层,抽走重复守卫/解析/错误映射
|
||||
位置: `hub/src/web/routes/{sessions.ts,messages.ts,git.ts,machines.ts,permissions.ts,auth.ts,bind.ts,events.ts}`, `hub/src/web/routes/guards.ts`
|
||||
问题: 几乎每个 route 都重复 `requireSyncEngine` / `requireSessionFromParam` / `await c.req.json().catch(() => null)` / `Invalid body` / try-catch error mapping;`sessions.ts` 已超大。
|
||||
方案: 提供统一 `parseJsonBody(schema)`、`withEngine`、`withSession`、`withActiveSession`、`withMachine`、`ApiRouteError`;把 `auth/bind` 共用 JWT 与 Telegram/accessToken 校验 service;把 `sessions.ts` 拆成 lifecycle/config/upload/read-only 多文件。
|
||||
影响范围: Hub HTTP API、错误语义、路由测试、后续接口扩展。
|
||||
|
||||
### [高] 简化 Web 实时更新契约,去掉 heuristic patching
|
||||
位置: `web/src/hooks/useSSE.ts`, `shared/src/sessionSummary.ts`, `hub/src/sync/sessionCache.ts`, `web/src/lib/message-window-store.ts`
|
||||
问题: `useSSE` 既做连接管理,又做 query patch,又靠 `hasUnknownSessionPatchKeys()` 决定是否回源;session summary contract 不完整,前端自己猜 patch 字段;第二套 store 增加复杂度。
|
||||
方案: 统一成一种事件协议: 要么总是发完整 `SessionSummary/MachineSummary`,要么发 shared 定义的最小 patch schema;删前端 heuristic;`message-window-store` 只保留聊天窗口分页/溢出职责,不再承担通用会话缓存修补。
|
||||
影响范围: Web SSE、Hub emit、TanStack Query cache、聊天刷新体验。
|
||||
|
||||
## 中优先级
|
||||
### [中] 拆 Web 大组件/大解析器
|
||||
位置: `web/src/{App.tsx,router.tsx}`, `web/src/components/{SessionChat.tsx,AssistantChat/HappyComposer.tsx,ToolCard/ToolCard.tsx}`, `web/src/chat/normalizeAgent.ts`
|
||||
问题: 400-800 行文件过多;单文件同时处理 auth、theme、push、SSE、voice、chat controller、composer、message normalization;修改风险高。
|
||||
方案: 拆成 app shell、auth bootstrap、sessions layout、chat controller hook、composer state/actions hook、按 envelope/flavor 分类的 normalizer。
|
||||
影响范围: Web 启动流程、聊天页、语音、路由层、单测。
|
||||
|
||||
### [中] 解耦 ToolCard registry/result renderer,打断 11 文件循环
|
||||
位置: `web/src/components/ToolCard/views/{_all.tsx,_results.tsx,*View.tsx}`, `web/src/components/ToolCard/{ToolCard.tsx,knownTools.tsx}`
|
||||
问题: `_all.tsx` 提供类型并注册组件,`_results.tsx` 又反向依赖它;各 ToolView 统一从 `_all.tsx` 拿类型,导致大环。
|
||||
方案: 新建 `views/types.ts`、`views/registry.ts`、`views/resultRegistry.ts`;每个工具旁置 input/result adapter;`ToolCard` 仅负责布局与调度。
|
||||
影响范围: Tool card 渲染、结果视图、测试、未来新增工具。
|
||||
|
||||
### [中] 抽重复 util/query 逻辑,减少同构代码
|
||||
位置: `web/src/hooks/queries/*.ts`, `web/src/hooks/queries/{useSkills.ts,useSlashCommands.ts}`, `cli/src/cursor/{cursorLocalLauncher.ts,cursorRemoteLauncher.ts}`, `cli/src/modules/common/handlers/uploads.ts`, `hub/src/web/routes/sessions.ts`
|
||||
问题: Query hook 返回结构重复;`levenshteinDistance` 重复;Cursor permissionMode 映射重复;base64 大小估算重复。
|
||||
方案: 建 `createApiQuery`、`fuzzyMatch.ts`、`cursorPermissionMode.ts`、`uploadSize.ts` 等共享 util;保留差异逻辑在调用层。
|
||||
影响范围: Web 查询层、CLI Cursor 模式、上传流程。
|
||||
|
||||
### [中] 简化配置生命周期,去掉可变 singleton/proxy
|
||||
位置: `cli/src/configuration.ts`, `hub/src/configuration.ts`, `cli/src/persistence.ts`, `hub/src/config/*`
|
||||
问题: `configuration._setApiUrl()`、`_setCliApiToken()`、Proxy 暴露未初始化对象;配置对象可变,测试/启动顺序敏感。
|
||||
方案: 改成显式 `loadConfig()` + 只读对象;初始化后通过依赖注入传递;shared 保留持久化 schema,包内只做特有字段扩展。
|
||||
影响范围: CLI/Hub 启动、测试隔离、未来并发初始化。
|
||||
|
||||
## 低优先级
|
||||
### [低] 统一命名,去掉 Happy/Server/Codex 历史残留
|
||||
位置: `README.md`, `cli/src/commands/claude.ts`, `shared/src/modes.ts`, `web/src/App.tsx`, 相关文档/帮助文本
|
||||
问题: `hub/server`、`Happy/HAPI`、agent payload type=`codex` 混用;认知负担高。
|
||||
方案: 统一外部命名 `hub`、内部 envelope 命名 `agent`/`tool-call`;文档、帮助、日志同一术语。
|
||||
影响范围: 文档、日志、调试、shared 常量。
|
||||
|
||||
### [低] 第三方集成抽服务
|
||||
位置: `hub/src/web/routes/voice.ts`, `hub/src/telegram/bot.ts`, `hub/src/notifications/*`
|
||||
问题: route/bot 文件内直接持有外部 API 调用、缓存、日志;边界不清。
|
||||
方案: 抽 `voiceService`、`telegramNotificationService`、`externalClients/*`;route 只做 I/O 映射。
|
||||
影响范围: Voice、Telegram、通知测试。
|
||||
|
||||
## 测试计划
|
||||
- 协议层: shared schema/DTO 快照;Hub route response contract test;CLI/Web 编译期类型收敛检查。
|
||||
- Hub 核心: session alive/expire/dedup/merge、namespace 隔离、SSE broadcast、route helper 错误映射。
|
||||
- CLI runtime: 每个 agent 至少覆盖 local/remote 切换、abort/switch、resume/sessionId 写回、权限模式映射。
|
||||
- Web: `useSSE` 连接/重连/patch、ToolCard registry/result、NewSession 创建流、SessionChat/Composer 关键交互。
|
||||
- 回归门槛: `bun run typecheck`、`bun run test` 必跑;新增重构只补边界/契约测试,不铺大而全 UI 快照。
|
||||
|
||||
## 假设与默认
|
||||
- 默认不保留任何向后兼容: 旧命令、旧设置字段、旧 DB schema、旧消息 envelope 均可删除或强制迁移。
|
||||
- 默认以 `hub/` 作为 server 包继续重构,不新建平行 `server/` 目录。
|
||||
- 默认保留现有行为语义;优先改结构,不主动改产品功能或交互。
|
||||
- 默认删除“确认未引用且无动态入口”的文件;若某文件仅靠发布脚本/二进制入口使用,先补显式引用再决定去留。
|
||||
Reference in New Issue
Block a user