From 5516472b099cde99e6fbaae34727baa961bb1fae Mon Sep 17 00:00:00 2001 From: James Feng <47167674+GhostDragon124@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:59:52 +0800 Subject: [PATCH 1/4] =?UTF-8?q?docs:=20archive=20this=20branch=20=E2=80=94?= =?UTF-8?q?=20Anthropic=20event=20sourcing=20snapshot=20before=20blackboar?= =?UTF-8?q?d=20rewrite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/BRANCH_EVENT_SOURCING.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docs/BRANCH_EVENT_SOURCING.md diff --git a/docs/BRANCH_EVENT_SOURCING.md b/docs/BRANCH_EVENT_SOURCING.md new file mode 100644 index 00000000..bf8def16 --- /dev/null +++ b/docs/BRANCH_EVENT_SOURCING.md @@ -0,0 +1,34 @@ +# 分支:persist/coordinator-event-sourcing + +## 存了什么 + +这是 CCP 采用 **Anthropic 原版事件溯源方案** 的快照——纯 JSONL 事件日志 + fold 投影,没有任何 SQLite 黑板代码。 + +## 核心文件 + +| 文件 | 作用 | +|------|------| +| `src/coordinator/teamEventStore.ts` | Event 类型定义 + EventStore 接口 + LocalFileEventStore(JSONL) | +| `src/coordinator/teamProjection.ts` | `applyEvent()` + `projectTeamState()` + `renderTeamContext()` | +| `src/coordinator/eventStoreInstance.ts` | 单例 + auto-switching(本地 JSONL / 远程 HTTP) | +| `src/coordinator/remoteEventStore.ts` | HTTP 客户端 EventStore(Phase 2 跨机器) | +| `src/coordinator/eventHttpServer.ts` | Bun.serve 事件服务器 | +| `src/coordinator/coordinatorMode.ts` | Coordinator mode 启动 + compaction hook(**不含黑板代码**) | +| `docs/Coordinator_Event_Log_设计文档.md` | 中文设计文档 | +| `docs/Coordinator_Event_Log_Design_Doc.md` | 英文设计文档 | +| `docs/plans/2026-06-11-coordinator-event-log.md` | 实施计划 | + +## 为什么不继续用 + +讨论结论(2026-06-12): +- 事件溯源在 CCP 的约束下是正确的——因为没有 SQLite +- 但它有维护成本:新事件类型需要改 fold、checkpoint 逻辑、1400 行代码 +- Hermes 的黑板(SQLite + structured keys)证明了更简单的方案可行 +- 最终决定:事件日志 + fold → SQLite 黑板(kv 表 + events 表同一事务) + +## 为什么保留 + +- 这是 Anthropic/CCB 的原生设计思路,有参考价值 +- 事件溯源的语义(有类型的事件、结构化聚合)比 pure last-write-wins 更丰富 +- 未来如果需要在 CCP 中做完整审计链路,事件表的 schema 可以从这里借鉴 +- 符合 CCP "保留上游设计痕迹" 的理念 From b11e73951e6b89800e395f0af6cd72fc02a41e7f Mon Sep 17 00:00:00 2001 From: James Feng <47167674+GhostDragon124@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:42:07 +0800 Subject: [PATCH 2/4] fix: isolate third-party provider env vars in createAdapter tests afterEach now clears CLAUDE_CODE_USE_OPENAI / CLAUDE_CODE_USE_GEMINI / CLAUDE_CODE_USE_GROK so external env vars don't hijack adapter selection. Fixes CI test failures when these flags leak into the test environment. --- .../WebSearchTool/__tests__/adapterFactory.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/builtin-tools/src/tools/WebSearchTool/__tests__/adapterFactory.test.ts b/packages/builtin-tools/src/tools/WebSearchTool/__tests__/adapterFactory.test.ts index 4e5353d8..f44c852e 100644 --- a/packages/builtin-tools/src/tools/WebSearchTool/__tests__/adapterFactory.test.ts +++ b/packages/builtin-tools/src/tools/WebSearchTool/__tests__/adapterFactory.test.ts @@ -12,15 +12,25 @@ mock.module('src/utils/model/providers.js', () => ({ const { createAdapter } = await import('../adapters/index') const originalWebSearchAdapter = process.env.WEB_SEARCH_ADAPTER +const originalUseOpenAI = process.env.CLAUDE_CODE_USE_OPENAI +const originalUseGemini = process.env.CLAUDE_CODE_USE_GEMINI +const originalUseGrok = process.env.CLAUDE_CODE_USE_GROK afterEach(() => { isFirstPartyBaseUrl = true + // Restore WEB_SEARCH_ADAPTER if (originalWebSearchAdapter === undefined) { delete process.env.WEB_SEARCH_ADAPTER } else { process.env.WEB_SEARCH_ADAPTER = originalWebSearchAdapter } + + // Clear third-party provider flags so they don't hijack adapter selection + // (CI or local env may have CLAUDE_CODE_USE_OPENAI=1 set) + delete process.env.CLAUDE_CODE_USE_OPENAI + delete process.env.CLAUDE_CODE_USE_GEMINI + delete process.env.CLAUDE_CODE_USE_GROK }) describe('createAdapter', () => { From e26fb0e2d3130901bcf961942650b77969158c76 Mon Sep 17 00:00:00 2001 From: James Feng <47167674+GhostDragon124@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:45:34 +0800 Subject: [PATCH 3/4] ci: surface failed test names in CI output Previously tail -30 truncated failures that appeared before the summary. Now grep for (fail) markers when tests fail, so we can see what broke. --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f77ac03..41783da6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,13 @@ jobs: FAILS=$(grep -oE '[0-9]+ fail' /tmp/ci-test.log | tail -1 | grep -oE '[0-9]+') if [ -z "$FAILS" ]; then FAILS=0; fi echo "Failures: $FAILS (baseline: 0) | bun test exit: $TEST_EXIT" - # Show last 30 lines of test output + # Show failed tests first (they appear before the summary, which tail misses) + if [ "$FAILS" -gt 0 ]; then + echo "::group::Failed tests" + grep -E '\(fail\)' /tmp/ci-test.log || true + echo "::endgroup::" + fi + # Show last 30 lines for pass/fail summary tail -30 /tmp/ci-test.log if [ "$FAILS" -le 0 ]; then echo "✅ Test baseline OK" From 3ce4dc4d4d744a636b8a6bd937e9fd7b256870d8 Mon Sep 17 00:00:00 2001 From: James Feng <47167674+GhostDragon124@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:30:56 +0800 Subject: [PATCH 4/4] fix: use ...RealModule spread in LocalAgentTask mock to prevent isolate failures Every mock.module() now imports the real module first and spreads all exports, then overrides only the specific functions the test controls. Fixes 'Export named X not found' errors in isolate mode when transitive imports need exports not listed in the partial mock. CI has been failing since June 11 macOS runner update exposed this. 3979 pass, 0 fail in both normal and --isolate modes. --- .../__tests__/LocalAgentTask.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/tasks/LocalAgentTask/__tests__/LocalAgentTask.test.ts b/src/tasks/LocalAgentTask/__tests__/LocalAgentTask.test.ts index 7300cbc9..f387c723 100644 --- a/src/tasks/LocalAgentTask/__tests__/LocalAgentTask.test.ts +++ b/src/tasks/LocalAgentTask/__tests__/LocalAgentTask.test.ts @@ -9,7 +9,23 @@ const noop = () => {} mock.module('src/utils/debug.ts', debugMock) mock.module('src/utils/log.ts', logMock) +const RealSessionStorage = await import('src/utils/sessionStorage.js') +const RealDiskOutput = await import('src/utils/task/diskOutput.js') +const RealMessageQueueManager = await import('src/utils/messageQueueManager.js') +const RealState = await import('src/bootstrap/state.js') +const RealSpeculation = await import( + 'src/services/PromptSuggestion/speculation.js' +) +const RealCleanupRegistry = await import('src/utils/cleanupRegistry.js') +const RealAbortController = await import('src/utils/abortController.js') +const RealSdkProgress = await import('src/utils/task/sdkProgress.js') +const RealSdkEventQueue = await import('src/utils/sdkEventQueue.js') +const RealXml = await import('src/constants/xml.js') +const RealAnalytics = await import('src/services/analytics/index.js') +const RealCollapseReadSearch = await import('src/utils/collapseReadSearch.js') + mock.module('src/utils/sessionStorage.js', () => ({ + ...RealSessionStorage, getAgentTranscriptPath: (id: string) => `/tmp/transcripts/${id}.jsonl`, recordSidechainTranscript: async () => {}, recordQueueOperation: noop, @@ -17,6 +33,7 @@ mock.module('src/utils/sessionStorage.js', () => ({ })) mock.module('src/utils/task/diskOutput.js', () => ({ + ...RealDiskOutput, evictTaskOutput: noop, getTaskOutputPath: (id: string) => `/tmp/output/${id}`, initTaskOutputAsSymlink: async () => {}, @@ -26,12 +43,14 @@ mock.module('src/utils/task/diskOutput.js', () => ({ // Capture enqueuePendingNotification calls for verification const enqueuedNotifications: string[] = [] mock.module('src/utils/messageQueueManager.js', () => ({ + ...RealMessageQueueManager, enqueuePendingNotification: (cmd: any) => { enqueuedNotifications.push(cmd.value) }, })) mock.module('src/bootstrap/state.js', () => ({ + ...RealState, getSdkAgentProgressSummariesEnabled: () => false, getSessionId: () => 'test-session-001', getProjectRoot: () => '/test/project', @@ -40,15 +59,18 @@ mock.module('src/bootstrap/state.js', () => ({ })) mock.module('src/services/PromptSuggestion/speculation.js', () => ({ + ...RealSpeculation, abortSpeculation: noop, })) const cleanupFns: (() => void)[] = [] mock.module('src/utils/cleanupRegistry.js', () => ({ + ...RealCleanupRegistry, registerCleanup: () => noop, })) mock.module('src/utils/abortController.js', () => ({ + ...RealAbortController, createAbortController: () => new AbortController(), createChildAbortController: (parent: AbortController) => { const ac = new AbortController() @@ -58,14 +80,17 @@ mock.module('src/utils/abortController.js', () => ({ })) mock.module('src/utils/task/sdkProgress.js', () => ({ + ...RealSdkProgress, emitTaskProgress: noop, })) mock.module('src/utils/sdkEventQueue.js', () => ({ + ...RealSdkEventQueue, enqueueSdkEvent: noop, })) mock.module('src/constants/xml.js', () => ({ + ...RealXml, TASK_NOTIFICATION_TAG: 'task_notification', TASK_ID_TAG: 'task_id', TOOL_USE_ID_TAG: 'tool_use_id', @@ -79,6 +104,7 @@ mock.module('src/constants/xml.js', () => ({ })) mock.module('src/services/analytics/index.js', () => ({ + ...RealAnalytics, logEvent: noop, logEventAsync: async () => {}, stripProtoFields: (v: any) => v, @@ -88,6 +114,7 @@ mock.module('src/services/analytics/index.js', () => ({ })) mock.module('src/utils/collapseReadSearch.js', () => ({ + ...RealCollapseReadSearch, getToolSearchOrReadInfo: () => undefined, }))