สถาปัตยกรรม
Control Center ใช้สถาปัตยกรรมแบบคลีนที่ยึดฟีเจอร์เป็นหลัก พร้อมรูปแบบพอร์ตและอะแดปเตอร์ โค้ดเบสจัดโครงสร้างตามโดเมนธุรกิจ (ฟีเจอร์) และมีกฎการพึ่งพาที่เข้มงวดซึ่งบังคับใช้ด้วยการทดสอบสถาปัตยกรรมอัตโนมัติ
ไคลเอนต์แบบบางบนเซิร์ฟเวอร์แบบไม่มีส่วนติดต่อ
หัวข้อที่มีชื่อว่า “ไคลเอนต์แบบบางบนเซิร์ฟเวอร์แบบไม่มีส่วนติดต่อ”Control Center เป็นสถาปัตยกรรมไคลเอนต์แบบบาง ไม่มีไคลเอนต์ใดเปิดฐานข้อมูลเอง — โพรเซส cc_server แบบไม่มีส่วนติดต่อเป็นเจ้าของข้อมูล (ไฟล์ global.db ขนาดเล็กบวกไฟล์ SQLite หนึ่งไฟล์ต่อเวิร์กสเปซ ซึ่งเปิดแบบ lazy) และให้บริการข้อมูลนั้นผ่าน WebSocket RPC แอปเดสก์ท็อป เว็บบิลด์ และแอปคู่หูบนโทรศัพท์ ต่างเป็นตัวแสดงผลบนการเชื่อมต่อ RPC เดียวกันนั้น
เมื่อเปิดใช้งานครั้งแรก เดสก์ท็อปจะเลือกวิธีเชื่อมต่อกับเซิร์ฟเวอร์ของตัวเอง: ภายในเครื่อง (สปอว์น cc_server บนเครื่องนี้แล้วสื่อสารผ่านลูปแบ็ก — เป็นค่าเริ่มต้น) หรือ ระยะไกล (ต่อกับ cc_server ที่อื่นผ่าน WebSocket ที่ปลอดภัย) ไคลเอนต์เว็บเป็นแบบระยะไกลเสมอ ตัวแก้เส้นทางตอนบูต (bootstrap/server_backend.dart) เป็นผู้เลือกเส้นทางก่อนที่ Riverpod จะมีอยู่ แล้วเขียนทับ rpcClientProvider ด้วยไคลเอนต์ที่เชื่อมต่อแล้ว — ฟีเจอร์ทุกตัวจึงอ่านและเขียนผ่านเซิร์ฟเวอร์ แทนที่จะอ่านเขียนฐานข้อมูลในเครื่อง
ชั้น RPC เองเป็นแบบไร้สถานะ: เซสชันผ่านการยืนยันตัวตนแล้ว แต่ไม่ได้ผูกกับเวิร์กสเปซใด การเรียกที่ผูกกับเวิร์กสเปซทุกรายการจะแนบ workspace_id ของตัวเองมาในอาร์กิวเมนต์ และดิสแพตเชอร์จะปฏิเสธการเรียกเช่นนั้นหากไม่มีค่านั้น นั่นคือสิ่งที่ทำให้ไคลเอนต์สองตัวบนเซิร์ฟเวอร์เดียวกันถือเวิร์กสเปซที่ใช้งานอยู่ต่างกันได้ โดยไม่มี “เวิร์กสเปซปัจจุบัน” ที่เซิร์ฟเวอร์เก็บไว้ให้ขัดแย้งกัน — และยังหมายความว่าการแยกโดเมนถูกบังคับใช้ต่อการเรียกแต่ละครั้ง ไม่ใช่ต่อการเชื่อมต่อ
นี่คือเวิร์กสเปซของ Dart ที่ใช้ lockfile เดียวที่แก้ไขครบแล้ว ประกอบด้วยสมาชิกยี่สิบเอ็ดราย — แพ็กเกจสิบห้าและแอปหกตัว — โดยมีแอปเดสก์ท็อปและเว็บ (control_center) เป็นรากของเวิร์กสเปซ แอปทั้งหกคือไบนารีเซิร์ฟเวอร์แบบไม่มีส่วนติดต่อ (cc_server), ตัวรันงานของฟลีตแบบไม่มีส่วนติดต่อ (cc_worker), PWA บนโทรศัพท์ (cc_remote), โบรกเกอร์รีเลย์ WebSocket (cc_signaling_server), แกลเลอรี Widgetbook (cc_gallery) และโฮสต์เดโมสาธารณะ (cc_demo_server) ส่วนแพ็กเกจทั้งสิบห้าคือ cc_ui (ระบบดีไซน์), cc_domain (เคอร์เนลที่ใช้ร่วมกันซึ่งเป็น Dart ล้วน: เอนทิตี ออบเจกต์ค่า พอร์ต เหตุการณ์ และเลเยอร์โดเมนของทุกฟีเจอร์), cc_harness (เคอร์เนลเอเจนต์ในตัว: ลูปเอเจนต์แบบ Dart ล้วน — ข้อความ พอร์ตผู้ให้บริการ เครื่องมือ การย่อบริบท การชี้นำ ฮุก ซับเอเจนต์ — โดยไม่ใช้ dart:io และไม่พึ่งพา cc_* ตัวอื่นใด), cc_harness_runtime (อะแดปเตอร์รันไทม์ของฮาร์เนสในตัว: ผู้ให้บริการสตรีมแบบเฉพาะ VM การจัดการข้อมูลรับรอง OAuth/PKCE ที่เก็บข้อมูลรับรอง และชุดเครื่องมือทั่วไป — รันเอเจนต์ได้โดยไม่ต้องติดตั้ง CLI ภายนอก), cc_rpc (ทรานสปอร์ตฝั่งไคลเอนต์), cc_host (เคอร์เนลฝั่งเซิร์ฟเวอร์), cc_data (รีโพซิทอรีระยะไกล), cc_persistence (ฐานข้อมูลฝั่งเซิร์ฟเวอร์), cc_server_core (การประกอบแอปและเซิร์ฟเวอร์ + รันไทม์ของตัวตน/สถานะออนไลน์/ฟลีต/การประเมิน), cc_infra (อะแดปเตอร์แบบเฉพาะ VM), cc_mcp (เครื่องมือ MCP), cc_mcp_client (ไคลเอนต์สำหรับเซิร์ฟเวอร์ MCP ภายนอก โดยเชื่อมเครื่องมือของเซิร์ฟเวอร์เหล่านั้นเข้าสู่รีจิสทรีในเครื่อง), cc_markdown (เอนจิน markdown ที่อยู่ในรีโพ), cc_natives (โหนดปลายของ FFI) และ system_audio_capture ดู การปรับใช้และไคลเอนต์ สำหรับเมทริกซ์ไคลเอนต์แบบเต็มและสิ่งที่รันอยู่ที่ใด
กฎการพึ่งพา
หัวข้อที่มีชื่อว่า “กฎการพึ่งพา”Presentation → Application/Providers → Domain ← Infrastructure- เลเยอร์โดเมน: เอนทิตี ออบเจกต์ค่า อินเทอร์เฟซรีโพซิทอรี พอร์ต และบริการโดเมนที่เขียนด้วย Dart ล้วน ไม่มีการนำเข้าจากชั้นโครงสร้างพื้นฐานเลย (ไม่มี dio, drift หรือโมเดลเครือข่าย)
- เลเยอร์พรีเซนเทชัน: หน้าจอ วิดเจ็ต และนอติไฟเออร์ ไม่เข้าถึง drift/DAO/ชั้นข้อมูลโดยตรง ทุกอย่างไหลผ่านผู้ให้บริการ Riverpod → รีโพซิทอรี
- เลเยอร์โครงสร้างพื้นฐาน: อิมพลีเมนเตชันจริงของพอร์ตโดเมนและอินเทอร์เฟซรีโพซิทอรี โดยขึ้นกับโดเมน ไม่มีทางเป็นกลับกัน
โครงสร้างของฟีเจอร์
หัวข้อที่มีชื่อว่า “โครงสร้างของฟีเจอร์”รูปแบบเต็มของฟีเจอร์คือ:
feature_name/├── data/ # Repository implementations, data sources, services, DTOs, mappers├── domain/ # Entities, repository interfaces, ports, use cases├── presentation/ # Screens, widgets, notifiers└── providers/ # Riverpod providersมีฟีเจอร์ไม่กี่ตัวใต้ lib/features/ ที่มีครบทั้งสี่ไดเรกทอรีจริง ๆ และช่องว่างเหล่านั้นก็ให้ข้อมูลที่เป็นประโยชน์มากกว่าเกิดขึ้นโดยบังเอิญ เคอร์เนลที่ใช้ร่วมกันได้ดูดกลืน domain/ ของทุกฟีเจอร์เข้าไปไว้ใน cc_domain และไคลเอนต์แบบบางไม่มีอิมพลีเมนเตชันของรีโพซิทอรีอยู่เลย ฟีเจอร์ส่วนใหญ่ใต้ lib/ จึงมีเพียง presentation/ กับ providers/ เท่านั้น ไดเรกทอรี data/ หรือ domain/ ที่ยังเหลืออยู่ในไคลเอนต์จึงเป็นเครื่องหมายว่าส่วนนั้นเป็นงานฝั่งไคลเอนต์จริง ๆ
มีข้อยกเว้นที่ตั้งชื่อไว้สองข้อที่ควรรู้:
mcpมีเฉพาะผู้ให้บริการเท่านั้น ส่วน UI การตั้งค่าและสถานะของมันอยู่ในsettings/และตัวพื้นผิวเครื่องมือเองเป็นแพ็กเกจ (cc_mcp) ไม่ใช่โฟลเดอร์ฟีเจอร์orchestrationและplan_studioมีเพียงpresentation/กับproviders/
ฟีเจอร์ guardrails วางเลเยอร์โดเมนไว้ที่ cc_domain/features/guardrails/ แทนที่จะไว้ในแอปราก เพราะเซิร์ฟเวอร์เป็นผู้บังคับใช้: กฎหนึ่งข้อจับคู่ ActionClass (หรือ commandPrefix ของเชลล์) เข้ากับการตัดสินใจอย่างใดอย่างหนึ่ง — allow, prompt หรือ deny — ณ สโคปหนึ่ง กฎถูกเก็บแยกตามเวิร์กสเปซ และทุกการกระทำของเอเจนต์ที่อยู่ภายใต้การกำกับจะคลี่คลายผ่านสายนโยบายก่อนที่จะรัน การคลี่คลายไล่จาก space > agent > workspace > mode preset > built-in default และ สโคปแรกที่มีกฎตรงกันเป็นตัวตัดสิน; การเลือกตัวที่เข้มงวดที่สุดเป็นเพียงตัวตัดสินเสมอเมื่ออยู่ภายในสโคปเดียวกัน และเมื่อต้องรวมหลายคลาสที่การกระทำหนึ่งประกาศไว้ พื้นผิวที่ผู้ปฏิบัติงานเห็นคือ การตั้งค่า → เวิร์กสเปซ → สิทธิ์ของเอเจนต์ ที่ /workspaces/<id>/settings/workspace/permissions ดู กฎคุม สำหรับโมเดลนี้
เคอร์เนลที่ใช้ร่วมกัน
หัวข้อที่มีชื่อว่า “เคอร์เนลที่ใช้ร่วมกัน”packages/cc_domain/lib/core/domain/ เก็บเอนทิตีและรีโพซิทอรีที่ใช้ร่วมกันตั้งแต่ 3 ฟีเจอร์ขึ้นไป เป็นโค้ด Dart ล้วนที่ไม่พึ่งพาโครงสร้างพื้นฐานใด ๆ จึงทำให้ทั้งไคลเอนต์ Flutter และไบนารีเซิร์ฟเวอร์ที่ไม่ใช้ Flutter นำนิยามชุดเดียวกันไปใช้ได้:
- เอนทิตีหลัก:
Agent,AgentRunLog,Workspace,Repo,ReviewSpaceAssociation - ตัวตนและการทำงานร่วมกันแบบหลายคน:
User,Principal(sealedUserPrincipal|AgentPrincipal),WorkspaceMember,WorkspaceRole - เอนทิตีหน่วยความจำ:
MemoryFact,MemoryPolicy,AgentWorkingMemory,MemoryAccessGrant - ออบเจกต์ค่าที่ใช้ร่วมกัน:
AgentSkills,AgentRole,Mode,SandboxBackend,SandboxSpec,RunCost - พอร์ตที่ใช้ร่วมกัน:
SandboxPort,CredentialBrokerPort,WorkspaceFilesystemPort,GitRepoInspectorPort,EmbeddingPort - บริการโดเมน:
MemoryAccessPolicy,ActivityLogger,MentionResolver,RunLivenessClassifier DomainEventBus+ ชนิดเหตุการณ์ (เวิร์กสเปซ/เอเจนต์, PR/รีวิว, การส่งข้อความ, ตั๋วงาน, ไปป์ไลน์, การประสานงาน, หน่วยความจำ, ปฏิทิน/การประชุม, ตัวตนและสมาชิกภาพ, การสังเกตการณ์)
ตัวตนและการทำงานร่วมกันแบบหลายคน
หัวข้อที่มีชื่อว่า “ตัวตนและการทำงานร่วมกันแบบหลายคน”Control Center รองรับผู้ใช้หลายคน: มนุษย์และเอเจนต์เป็นผู้กระทำที่มีศักดิ์เท่ากัน รวมเป็นหนึ่งเดียวกันด้วย Principal สมาชิกเวิร์กสเปซคือ User ที่ผูกกับ Workspace ผ่าน WorkspaceRole (owner/admin/member/viewer/guest) สมาชิกภาพคือขอบเขตของการเข้าถึง ไม่ใช่คีย์จับคู่ และการให้สิทธิ์รายรีโพซิทอรีช่วยไม่ให้สมาชิกภาพของเวิร์กสเปซให้สิทธิ์เกินกว่าโฮสต์โค้ด ผู้ใช้คนแรกที่ถูกสร้างบนเซิร์ฟเวอร์ใหม่จะเป็นเจ้าของการติดตั้งนี้ คนอื่นเข้าร่วมด้วยคำเชิญหรือ OIDC การสร้างเวิร์กสเปซจะประทับผู้ใช้คนนั้นเป็น owner
การทำงานร่วมกันแบบเรียลไทม์ใช้แนวทาง เซิร์ฟเวอร์เป็นผู้ตัดสิน + ผู้เขียนหลังสุดชนะ (LWW) แยกต่อฟิลด์ ไม่ใช่ CRDT สถานะออนไลน์ (ใครอยู่ตรงนี้ อยู่ตรงไหน เคอร์เซอร์ การพิมพ์ สถานะสดและค่าใช้จ่ายที่กำลังรันของเอเจนต์) เป็นช่องทางชั่วคราวที่แยกออกไปและไม่ถูกบันทึกถาวรเลย; ส่วนสถานะถาวรใช้แกนกลางแบบปรับเปลี่ยนแบบมองโลกในแง่ดี + ให้เซิร์ฟเวอร์รีเบส + LWW มนุษย์และเอเจนต์ใช้รายชื่อชุดเดียวกัน เมื่อมีผู้ปฏิบัติงานที่เป็นมนุษย์เพียงคนเดียว ช่องทางสถานะออนไลน์จะอยู่นิ่งและไม่มีองค์ประกอบส่วนติดต่อของรายชื่อปรากฏขึ้น — การทำงานร่วมกันแบบหลายคนไม่ทำให้ผู้ใช้ที่ทำงานคนเดียวต้องเสียอะไรเลย
พอร์ตและอะแดปเตอร์
หัวข้อที่มีชื่อว่า “พอร์ตและอะแดปเตอร์”พอร์ตคืออินเทอร์เฟซแบบนามธรรมในเลเยอร์โดเมน ส่วนอะแดปเตอร์คืออิมพลีเมนเตชันจริงในชั้นโครงสร้างพื้นฐาน เนื่องจากเซิร์ฟเวอร์แบบไม่มีส่วนติดต่อเป็นเจ้าของข้อมูลและต้องบิลด์ได้โดยไม่ใช้ Flutter อะแดปเตอร์เกือบทั้งหมดจึงใช้ได้เฉพาะบน VM และอยู่ใน packages/cc_infra/lib/src/ โดยจัดกลุ่มตามเรื่องที่ดูแล:
| พอร์ต | ตำแหน่งอะแดปเตอร์ |
|---|---|
SandboxPort |
cc_infra/lib/src/sandboxing/ (native_sandbox_adapter.dart) |
CredentialBrokerPort |
cc_infra/lib/src/sandboxing/ (env_credential_broker.dart, github_fine_grained_broker.dart) |
GitRepoInspectorPort |
cc_infra/lib/src/git/ (git_repo_inspector.dart) |
EmbeddingPort |
cc_infra/lib/src/embedding/ (embedding_service.dart) |
NotificationPort |
lib/core/notifications/ (ฝั่งไคลเอนต์: เรนเดอร์เหตุการณ์จากเซิร์ฟเวอร์ในเครื่อง) |
AgentBackend |
cc_infra/lib/src/dispatch/backends/ (acp_backend.dart, harness_backend.dart, cli_backends.dart) |
TicketProviderPort |
cc_infra/lib/src/tickets/linear/ (LinearTicketAdapter) Jira, ClickUp และ GitHub Issues เป็นอะแดปเตอร์ซิงก์ตั๋วงานใน cc_infra/lib/src/tickets/sync/; ตั๋วงานในเครื่องอยู่ในฐานข้อมูลของเวิร์กสเปซโดยไม่มี TicketProviderPort |
PipelineEnginePort |
cc_domain features/pipelines/domain/services/ (บริการโดเมนที่เขียนด้วย Dart ล้วน — ไม่ต้องใช้โครงสร้างพื้นฐานเลย) |
รากการประกอบของแอป (di/providers.dart) ผูกพอร์ตฝั่งไคลเอนต์เข้ากับอิมพลีเมนเตชันผ่านผู้ให้บริการ Riverpod ส่วนเซิร์ฟเวอร์ประกอบอะแดปเตอร์ของตัวเองใน cc_server_core
การจัดการสถานะ
หัวข้อที่มีชื่อว่า “การจัดการสถานะ”Riverpod สำหรับสถานะฝั่งไคลเอนต์ทั้งหมด:
Notifier<T>และAsyncNotifier<T>สำหรับสถานะที่เปลี่ยนแปลงได้FutureProvider<T>สำหรับข้อมูลแบบอะซิงโครนัส- รายการและแถวข้อมูลสดมาถึงผ่านการสมัครรับข้อมูล RPC ในรูป
AsyncValue<List<T>>— เซิร์ฟเวอร์ใช้ Drift.watch()ภายใน ส่วนไคลเอนต์ไม่เปิด Drift - เครื่องมือ MCP รับดีเพนเดนซีเป็นพารามิเตอร์ของคอนสตรักเตอร์แบบมีชนิดข้อมูล ไม่ใช้
Ref
ฐานข้อมูล
หัวข้อที่มีชื่อว่า “ฐานข้อมูล”Drift (SQLite) แยกเป็นสองฐานข้อมูล โดยทั้งคู่อยู่ใน cc_persistence:
GlobalDatabase(<dataDir>/global.db) — ตารางสั้น ๆ ที่เป็นของทั้งเซิร์ฟเวอร์จริง ๆ ได้แก่ ทะเบียนเวิร์กสเปซ, ผู้ใช้/ค่ากำหนด/อุปกรณ์ที่จับคู่, การเชื่อมต่อ SSO, การตั้งค่าเซิร์ฟเวอร์ระดับการติดตั้ง, นโยบายการกระทำแบบมีการจัดการ (กลไกจำกัดระดับการติดตั้งที่ทำได้เพียงรัดกุมขึ้นเท่านั้นเมื่อเทียบกับกฎคุมของเวิร์กสเปซ), ฟีดข่าวรายผู้ใช้, คิวของฟลีต (วอร์กเกอร์/งาน/บันทึกการจัดวาง), ดัชนีworkspace_routesก่อนการยืนยันตัวตน และเมทาดาทาของการติดตั้ง ตอนบูตจะเปิดเฉพาะไฟล์นี้เท่านั้น ต้นทุนการเริ่มระบบจึงคงที่ ไม่ว่าเวิร์กสเปซจะสะสมประวัติไว้มากแค่ไหน การเพิ่มตารางที่นี่เป็นการตัดสินใจด้านการแยกโดเมนที่การทดสอบแบบแรตเช็ตบังคับให้คุณต้องออกมาถกเหตุผลWorkspaceDatabase(<dataDir>/<workspaceId>/workspace.db) — หนึ่งไดเรกทอรีต่อหนึ่งเวิร์กสเปซ บรรจุไฟล์ SQLite หนึ่งไฟล์พร้อมทุกอย่างที่เกี่ยวข้องกับเวิร์กสเปซนั้นเพียงแห่งเดียว เพื่อให้ worktree ของการสนทนาและไฟล์ของเอเจนต์วางอยู่ข้างฐานข้อมูลและถูกลบไปพร้อมกัน เกือบทุกอย่างอยู่ที่นี่: เอเจนต์ สเปซ ตั๋วงาน หน่วยความจำ ไปป์ไลน์ การประชุม กราฟโค้ด รีวิว รีโพซิทอรี ไฟล์จะถูกเปิดแบบ lazy เมื่อถูกเรียกใช้ครั้งแรกผ่านWorkspaceDatabaseManager.of(workspaceId)และจะจ่ายquick_check, การติดตั้ง FTS/ทริกเกอร์ และvector_initของตัวเองตอนนั้น
ทั้งสองส่วนเริ่มจากเส้นฐาน v1 ที่ถูกยุบรวม — onCreate สร้างสคีมาปัจจุบัน — โดยจะเพิ่มขั้นตอนการย้ายข้อมูลต่อท้ายเฉพาะเมื่อฐานข้อมูลที่ติดตั้งใช้งานอยู่ต้องถูกพาไปข้างหน้า
เพราะหนึ่งเวิร์กสเปซคือหนึ่งไฟล์ การนำเวิร์กสเปซออกจึงเป็นคำสั่ง VACUUM INTO เดียว (workspace.export) ไม่ใช่การดัมป์ทีละตาราง; ส่วน workspace.import จะรับไฟล์แบบนั้นมาใช้ และอาศัย workspace_meta ที่ฝังอยู่ในไฟล์เพื่อแยกว่าจะนำไฟล์ของตัวเองกลับมาใช้ใหม่ หรือรับไฟล์ที่มาจากการติดตั้งอื่น
ฐานข้อมูลทั้งสองเป็นของ cc_persistence และถูกเปิดโดย cc_server เท่านั้น ไม่มีไคลเอนต์ใดเปิด DB — ไคลเอนต์ทุกตัวอ่าน/เขียนผ่าน RPC ตารางถูกนิยามไว้ใน packages/cc_persistence/lib/database/tables/ ส่วน DAO อยู่ใน .../daos/; เอนทิตีของโดเมนเป็น Dart ล้วน แยกจากคลาสตารางของ Drift โดยมีการแมปในชั้นข้อมูล FTS5 ดูแลการค้นหาแบบเต็มข้อความ และ sqlite_vector ดูแลการฝังเวกเตอร์ ซึ่งทั้งคู่ทำงานฝั่งเซิร์ฟเวอร์เท่านั้น; ฐานข้อมูลทั้งสองรัน PRAGMA foreign_keys=ON ในโหมดบันทึกแบบ WAL
การกำหนดเส้นทาง
หัวข้อที่มีชื่อว่า “การกำหนดเส้นทาง”go_router โดยมีข้อเท็จจริงหนึ่งข้อที่ครอบงำส่วนที่เหลือทั้งหมด: ทุกปลายทางในแอปมีคำนำหน้าเป็นเวิร์กสเปซ ในรูป /workspaces/:workspaceId/… ไอดีเวิร์กสเปซใน URL เป็นแหล่งความจริงเดียวสำหรับเวิร์กสเปซที่ใช้งานอยู่ — activeWorkspaceIdProvider ถูกขับจากเส้นทาง ไม่ใช่ทางกลับกัน — จึงเป็นเหตุผลว่าทำไมการอ่านข้อมูลที่ผูกกับเวิร์กสเปซนอกเส้นทางที่มีคำนำหน้าจึงไม่มีที่มาของไอดี
- ฟังก์ชัน สร้างเส้นทางใน
router/routes.dartโดยแต่ละตัวรับไอดีเวิร์กสเปซเป็นอาร์กิวเมนต์ตัวแรก ไม่ใช่ค่าคงที่สตริง - มีสี่เส้นทางก่อนเข้าบริบทที่ไม่มีคำนำหน้า:
/splash,/onboarding,/signed-outและ/workspaces(ตัวเลือกเวิร์กสเปซ) ShellRouteครอบเชลล์ของแอป; พื้นผิวทั้งสี่นั้นเรนเดอร์เต็มหน้าจอนอกเชลล์- ตัวกั้นการยืนยันตัวตน: การตั้งค่าที่ยังไม่สมบูรณ์จะไปที่
/onboarding; การตั้งค่าที่สมบูรณ์แล้วแต่ข้อมูลรับรองของโฮสต์โค้ดหายไปจะไปที่/signed-outคำว่าสมบูรณ์หมายถึงมีโฮสต์โค้ดอย่างน้อยหนึ่งแห่งที่เชื่อมต่อไว้สำหรับผู้ใช้ที่ลงชื่อเข้าใช้ และมีเวิร์กสเปซอย่างน้อยหนึ่งแห่ง — ไม่ได้เจาะจงว่าเป็น GitHub และ CLIghไม่ใช่วิธีการยืนยันตัวตน - ด่านการเริ่มต้นใช้งานอยู่ใน
features/auth/providers/
เครือข่าย (ฝั่งเซิร์ฟเวอร์)
หัวข้อที่มีชื่อว่า “เครือข่าย (ฝั่งเซิร์ฟเวอร์)”HTTP ภายนอกทั้งหมดอยู่ที่เซิร์ฟเวอร์ ไคลเอนต์ไม่เคยต่อตรงไปยัง GitHub, Linear หรือ Google — แต่จะเรียกอ็อป RPC ของเซิร์ฟเวอร์ และแม้แต่สื่อจากระยะไกลก็ถูกดึงผ่านปลายทาง /proxy/media ของเซิร์ฟเวอร์ แทนที่จะดึงจากต้นทางโดยตรง
- ไคลเอนต์ dio ใน
packages/cc_infra/lib/src/:GitHubApiClient,GitHubPrClient,GitHubContentClient,GitHubGraphQLClient,GitLabApiClient,BitbucketApiClient,LinearGraphQlClientและGoogleCalendarApiClient - การฉีดโทเค็นยืนยันตัวตนผ่านอินเทอร์เซปเตอร์ รวมถึงอินเทอร์เซปเตอร์ Google OAuth แยกตามบัญชีใน
cc_server_coreที่รีเฟรชโทเค็นเมื่อเจอ 401 - ข้อผิดพลาดทั้งหมดถูกแมปผ่าน
packages/cc_infra/lib/src/network/error_mapper.dart→ คลาสย่อยของAppExceptionที่มีชนิดข้อมูลชัดเจนซึ่งนิยามไว้ในcc_domain
ความปลอดภัย
หัวข้อที่มีชื่อว่า “ความปลอดภัย”เซิร์ฟเวอร์เป็นผู้ถือข้อมูลรับรองที่สำคัญ: โทเค็น GitHub และโทเค็นของระบบตั๋วงานที่ใช้ในการสั่งเอเจนต์ คีย์ของผู้ให้บริการ LLM และโทเค็น OAuth ล้วนอยู่ในไดเรกทอรีข้อมูลของเซิร์ฟเวอร์หรือสภาพแวดล้อมของมัน ส่วนสิ่งที่พวงกุญแจของไคลเอนต์ยังถืออยู่คือข้อมูลรับรองของอุปกรณ์เอง และคีย์จับคู่สำหรับเซิร์ฟเวอร์ระยะไกล
- ความลับใน
flutter_secure_storage(พวงกุญแจ macOS, ตัวจัดการข้อมูลรับรอง Windows, libsecret บน Linux) — ข้อมูลรับรองของอุปกรณ์ฝั่งไคลเอนต์และคีย์จับคู่ระยะไกล ไม่ใช่โทเค็นของผู้ให้บริการ shared_preferencesสำหรับค่ากำหนดที่ไม่ละเอียดอ่อนเท่านั้น (ธีม ฟอนต์ เลย์เอาต์)- ความลับของผู้ให้บริการที่ตกค้างจากการติดตั้งเวอร์ชันเก่าจะถูกส่งมอบให้เซิร์ฟเวอร์หนึ่งครั้งโดย
features/auth/providers/credential_migration.dartแล้วลบออกจากพวงกุญแจของไคลเอนต์
การบังคับใช้สถาปัตยกรรม
หัวข้อที่มีชื่อว่า “การบังคับใช้สถาปัตยกรรม”ข้อจำกัดด้านสถาปัตยกรรมถูกตรวจสอบโดย test/core/architecture_constraints_test.dart ซึ่งจะล้มเหลวหากมีการฝ่าฝืนกฎการพึ่งพา
แนวคิดที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “แนวคิดที่เกี่ยวข้อง”- เวิร์กสเปซและการแยกโดเมน: การแยกโดเมนถูกบังคับใช้อย่างไร
- วงจรชีวิตการสั่งงานเอเจนต์: ขั้นตอนการสั่งงาน
- เหตุการณ์โดเมน: การสื่อสารระหว่างฟีเจอร์
- การปรับใช้และไคลเอนต์: โมเดลเซิร์ฟเวอร์กับไคลเอนต์แบบบางและเมทริกซ์ไคลเอนต์