Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
34 changes: 34 additions & 0 deletions docs/BRANCH_EVENT_SOURCING.md
Original file line number Diff line number Diff line change
@@ -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 "保留上游设计痕迹" 的理念
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
27 changes: 27 additions & 0 deletions src/tasks/LocalAgentTask/__tests__/LocalAgentTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,31 @@ 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,
writeAgentMetadata: async () => {},
}))

mock.module('src/utils/task/diskOutput.js', () => ({
...RealDiskOutput,
evictTaskOutput: noop,
getTaskOutputPath: (id: string) => `/tmp/output/${id}`,
initTaskOutputAsSymlink: async () => {},
Expand All @@ -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',
Expand All @@ -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()
Expand All @@ -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',
Expand All @@ -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,
Expand All @@ -88,6 +114,7 @@ mock.module('src/services/analytics/index.js', () => ({
}))

mock.module('src/utils/collapseReadSearch.js', () => ({
...RealCollapseReadSearch,
getToolSearchOrReadInfo: () => undefined,
}))

Expand Down
Loading