| name | knowledge-runtime-state-bootstrap-and-observability |
| description | 覆盖 Blade 的进程启动目录状态、跨异步上下文 cwd 隔离、共享 Vanilla Store、日志与运行环境观测、运行时包信息、Session ID、原生目录选择和项目注册。 使用时机:调整 CLI 启动顺序、排查多 workspace 状态串扰、从 React 外访问 Store、增加运行态观测、修改版本识别、Session 标识或项目选择/绑定流程。 不包含:配置层级与持久化路由见 layered-configuration-and-runtime-settings,事件日志与持久投影见 durable-transcript-and-event-projection,容量和关闭编排见 capacity-lifecycle-and-egress。 关键词:bootstrap state, getCwd, runWithCwdOverride, vanillaStore, ensureStoreInitialized, Logger, streamDebug, getEnvironmentContext, getVersion, createSessionId, NativeDirectoryPicker, ProjectRegistry。
|
Module Structure
该组件提供各运行表面共享的进程级基础状态与观测入口;它区分启动目录、当前工作区和稳定项目根,并把 UI 状态、日志、环境探测及项目发现收敛到可被 Agent、CLI、Server、工具和扩展复用的 API。
Directory Layout
packages/cli/src/bootstrap/state.ts — 惰性初始化的 cwd、originalCwd 与 projectRoot 三态
packages/cli/src/store/ — React 与非 React 共用的 Zustand Store、切片、类型和细粒度选择器
packages/cli/src/logging/ — 分类 JSONL 日志与独立流式调试日志
packages/cli/src/utils/cwd.ts — 基于 AsyncLocalStorage 的并发 cwd 覆写
packages/cli/src/utils/environment.ts — 项目根发现、环境提示和目录概览
packages/cli/src/utils/packageInfo.ts — 源码与打包布局兼容的运行时包信息
packages/cli/src/utils/sessionId.ts — 可安全用于存储路径的 Session ID
packages/cli/src/services/DirectoryPicker.ts — macOS、Windows 与 Linux 原生目录选择
packages/cli/src/services/ProjectRegistry.ts — 规范化项目路径的持久绑定注册表
Key Entry Points
getCwd() / runWithCwdOverride() — 获取当前执行上下文的 workspace,或为异步调用链临时覆写
vanillaStore / useBladeStore() / ensureStoreInitialized() — 共享状态实例、React 订阅与幂等配置初始化
createLogger() / setLoggerSessionId() / streamDebug() — 分类日志、会话日志路由与流式专项诊断
getEnvironmentContext() / getVersion() / createSessionId() — 构造环境上下文、读取运行版本和生成存储安全标识
NativeDirectoryPicker.pick() / ProjectRegistry.list() — 选择本机目录并读取规范化项目集合
API Surface
Workspace Location
getCwd() — 优先返回当前 AsyncLocalStorage 覆写,否则返回进程级 cwd
runWithCwdOverride(cwd, fn) — 在同步及后续异步调用链内隔离 workspace
getOriginalCwd() — 返回 CLI 被调用时的原始目录,用于解析用户传入的相对路径
getProjectRoot() — 返回启动后保持稳定的项目标识根
findProjectRoot(startDir) / setCwd(newPath, relativeTo?) — 发现工作区根并更新规范化的全局 cwd
Shared Store
useBladeStore(selector) — React 对共享 Vanilla Store 的细粒度订阅入口
getState() — Agent、服务与命令读取当前 Store 快照
sessionActions() / appActions() / configActions() — 面向不同状态域的动作入口
ensureStoreInitialized() — 以共享 Promise 合并并发配置初始化
useCurrentStreamingBuffer() — 以浅比较订阅流式行缓冲、尾部、计数和版本
Observability
createLogger(category, options?) — 创建分类日志器,文件输出与终端调试过滤相互独立
Logger.setGlobalDebug(config) — 动态设置全局终端日志开关或分类过滤
setLoggerSessionId(sessionId) — 切换后续 JSONL 日志的文件名
streamDebug(source, message, data?) — 向独立流式诊断文件追加记录且不向调用方抛错
getEnvironmentContext(options?) — 组装系统、Git、脚本和目录信息供系统提示使用
Runtime Identity And Project Discovery
getPackageInfo() / getVersion() — 从运行时邻近 package.json 读取并缓存实际发布信息
createSessionId(prefix?, size?) — 校验前缀后生成可用于文件名的 ID
NativeDirectoryPicker.pick() — 跨平台打开至多一个并发原生目录选择器
ProjectRegistry.bind() / list() / unbind() — 规范化、持久化并查询项目绑定
Usage Examples
CLI 启动时固定项目根
const invocationCwd = process.cwd();
const detectedRoot = findProjectRoot(invocationCwd);
setCwd(detectedRoot);
setProjectRoot(getCwd());
启动 Server 前确保共享 Store 可用
await ensureStoreInitialized();
const server = await BladeServer.listenAsync(opts);
TUI 初始化完成后绑定会话日志
const state = getState();
const sessionId = state.session.sessionId;
setLoggerSessionId(sessionId);
Gotchas
originalCwd、cwd 与 projectRoot 不能互换:CLI 相对参数以调用目录解析,运行工具使用可变 cwd,历史、Skill 和 Session 等项目身份使用启动后固定的 projectRoot (packages/cli/src/bootstrap/state.ts, packages/cli/src/blade.tsx, packages/cli/src/cli/settings.ts, packages/cli/src/mcp/loadMcpConfig.ts)
- CLI 必须先执行
findProjectRoot()、setCwd() 和 setProjectRoot() 再启动依赖 workspace 的流程,否则配置、信任与资源发现会以调用子目录为根 (packages/cli/src/blade.tsx, packages/cli/src/utils/environment.ts)
- 并发 Session 或子代理不能用全局
setCwd() 做临时切换;应使用 runWithCwdOverride(),其 AsyncLocalStorage 覆写会沿异步调用链传播且不污染其他执行上下文 (packages/cli/src/utils/cwd.ts, git:4543542b)
setCwd() 解析相对路径时默认基于真实 process.cwd() 而不是当前 AsyncLocalStorage 覆写;在隔离上下文内传相对路径必须显式提供 relativeTo (packages/cli/src/utils/environment.ts, packages/cli/src/utils/cwd.ts)
- Store 是否初始化仅以
config.config !== null 判断;CLI、Server、ACP 和 Hook 等非 React 入口在读取配置前都必须等待 ensureStoreInitialized(),并发调用会共享同一个初始化 Promise (packages/cli/src/store/vanilla.ts, packages/cli/src/commands/serve.ts, packages/cli/src/acp/BladeAgent.ts)
resetSession() 只重置 Zustand 内的 Session 字段并保留 sessionId,不会隐式清空模块级 conversationProjection;切换事件流时还要显式调用 resetConversationProjection() (packages/cli/src/store/slices/sessionSlice.ts)
Logger 的 debug 开关只控制终端输出,所有级别仍同步追加到 JSONL;不要通过关闭 debug 假设磁盘日志已停用 (packages/cli/src/logging/Logger.ts)
- 日志文件路由使用进程级
currentSessionId 而非异步上下文;TUI 绑定 Session 前的记录进入 blade-default.jsonl,该机制也不能区分并发 Server Session (packages/cli/src/logging/Logger.ts, packages/cli/src/ui/App.tsx)
- 日志目录初始化只尝试一次;权限或创建失败后会缓存不可用状态,当前进程内即使外部修复目录也不会自动重试 (
packages/cli/src/logging/Logger.ts)
streamDebug() 不受 Logger.setGlobalDebug() 控制,并会在每个 storage root 的首次调用时重写 stream-debug.log;它是临时专项诊断通道,不是普通分类日志 (packages/cli/src/logging/StreamDebugLogger.ts, )
Architecture
bootstrap/state.ts 保存进程级三态,cwd.ts 只在其上增加异步上下文覆盖;绝大多数业务代码应依赖 getCwd(),只有启动和测试夹具直接操作底层状态 (packages/cli/src/bootstrap/state.ts, packages/cli/src/utils/cwd.ts)
- 单一
vanillaStore 同时服务 Ink React 订阅和 Agent、服务、命令的命令式访问;Store 不使用 persist 中间件,配置和会话持久化分别交给 ConfigService 与 Context/JSONL (packages/cli/src/store/index.ts, packages/cli/src/store/vanilla.ts)
- Session Store 同时投影持久消息、临时流式缓冲和 Provider/停滞/重试等观测事件;命令结束或取消会集中清除易过期的运行态诊断,避免下一轮展示旧状态 (
packages/cli/src/store/slices/sessionSlice.ts, packages/cli/src/store/slices/commandSlice.ts, git:2aa2b22b, git:e6bd4e15)
getEnvironmentContext() 默认采集 Git 分支、工作树、最近提交、根目录清单和常用脚本;单项命令失败只省略对应片段,不会阻断提示构建 (packages/cli/src/utils/environment.ts, packages/cli/tests/unit/platform/utils/environment.test.ts, git:7680e3eb)
- 项目根发现会继续向上寻找
.git、.blade 或 .claude,仅在没有这些标记时回退到最近的 package.json,因此 monorepo 子包默认归属仓库根 (packages/cli/src/utils/environment.ts, packages/cli/tests/unit/platform/utils/environment.test.ts)
- 项目注册表以 realpath 作为去重身份,保留已失联项目供用户修复或解绑,并把当前项目固定排在名称排序之前 (
packages/cli/src/services/ProjectRegistry.ts, packages/cli/tests/unit/services/project-registry.test.ts)
Decisions
- cwd 隔离采用 AsyncLocalStorage 而不是
process.chdir(),使并行子代理、ACP 和真实 API 驱动可在同一进程内保持各自 workspace (packages/cli/src/utils/cwd.ts, packages/cli/tests/support/browserToolAcpDriver.ts)
- Zustand 只承担当前进程读模型,不承担磁盘权威状态;这一边界允许 Session 事件日志和配置服务独立处理恢复、原子写与版本迁移 (
packages/cli/src/store/vanilla.ts, packages/cli/src/store/types.ts)
- 包版本改为从源码或 bundle 邻近目录向上搜索名称匹配的 package.json,避免发布包仍报告构建时导入的旧版本;找不到时才使用导入值或
BLADE_VERSION (packages/cli/src/utils/packageInfo.ts, git:092d356b)
- Session ID 统一由带受限前缀的 nanoid 生成,替代可能包含路径非法字符的临时标识,使 TUI、Web、ACP、任务和子代理共享存储安全格式 (
packages/cli/src/utils/sessionId.ts, packages/cli/src/store/slices/sessionSlice.ts, git:d148a8fa)
Patterns
- React 消费者通过专用 selector 订阅最小状态片段,对对象组合使用
useShallow,空模型列表复用常量引用;非 React 消费者通过 getState() 和动作访问器共享同一实例 (packages/cli/src/store/index.ts, packages/cli/src/store/selectors/index.ts, packages/cli/src/store/vanilla.ts)
- Store action 在并发清理时使用身份或请求 ID 防止迟到回调覆盖新状态,例如
clearAbortController(expectedController) 和旁路对话完成动作都先核对当前所有者 (packages/cli/src/store/slices/commandSlice.ts, packages/cli/src/store/slices/appSlice.ts)
- 项目绑定写入遵循 mutex 内 read-modify-write、原子替换和
0600 文件权限;目录本身同步收紧到 0700 (packages/cli/src/services/ProjectRegistry.ts)
NativeDirectoryPicker.pick() 缓存进行中的 Promise,并在 settle 后清空,多个并发 Web 请求只会打开一个系统对话框 (packages/cli/src/services/DirectoryPicker.ts, packages/cli/tests/unit/platform/services/directory-picker.test.ts)
Dependencies
- 原生目录选择分别依赖 macOS
osascript、Windows PowerShell Forms、Linux zenity 或 kdialog,统一受 15 分钟超时和 64 KiB 输出上限约束 (packages/cli/src/services/DirectoryPicker.ts)
- 日志和项目注册都从 Blade storage root 派生文件位置;前者容忍写入失败,后者使用
write-file-atomic 并向调用方传播非 ENOENT/语法错误 (packages/cli/src/logging/Logger.ts, packages/cli/src/services/ProjectRegistry.ts)
Consumer Analysis
- TUI 是共享 Store 的最高频消费者,通过 selector 渲染消息、焦点和运行态,并通过动作协调初始化、模型、权限和日志 Session 绑定 (
packages/cli/src/ui/App.tsx, packages/cli/src/ui/hooks/useAgent.ts, packages/cli/src/store/selectors/index.ts)
- Server 与 CLI 命令在无 React 环境调用
ensureStoreInitialized()、配置动作、环境版本和项目注册服务,启动顺序错误会直接影响 Web/Headless 可用性 (packages/cli/src/commands/serve.ts, packages/cli/src/server/server.ts, packages/cli/src/server/routes/projects.ts)
- Agent、SessionRuntime 与子代理使用共享 Store 投影运行状态,并以
getCwd() 或 AsyncLocalStorage 覆写确定每次执行的 workspace (packages/cli/src/agent/Agent.ts, packages/cli/src/agent/runtime/SessionRuntime.ts, packages/cli/src/agent/subagents/BackgroundAgentManager.ts)
- 工具、Hooks、MCP、Plugins 和 LSP 广泛依赖
getCwd() 与分类 Logger;这些扩展必须优先使用显式 context.workspaceRoot,缺失时才回退进程上下文 (packages/cli/src/tools/execution/ToolExecutor.ts, packages/cli/src/hooks/HookManager.ts, packages/cli/src/plugins/PluginRegistry.ts, packages/cli/src/lsp/LspSessionManager.ts)
- SessionService、PersistentStore 和协议适配层消费运行版本与存储安全 ID,把同一身份格式写入会话元数据、JSONL 路径、ACP 握手和 MCP 客户端信息 (
packages/cli/src/services/SessionService.ts, packages/cli/src/context/storage/PersistentStore.ts, packages/cli/src/acp/BladeAgent.ts, packages/cli/src/mcp/McpClient.ts)