feat: support Codex and Claude local surfaces

This commit is contained in:
thanhnv
2026-07-24 16:33:21 +07:00
parent 34a6b013c0
commit cc666db9de
9 changed files with 119 additions and 26 deletions
+14 -4
View File
@@ -267,11 +267,21 @@ Không gọi một turn là CASAN-certified nếu không có trace tương ứng
## Client support
| Client | Chat bình thường tự qua CASAN | Bước bắt buộc |
| Client surface | Chat bình thường tự qua CASAN | Bước bắt buộc |
|---|---:|---|
| Claude Code CLI/extension | Có | Mở repository dưới dạng trusted project |
| Codex CLI/extension | Có | Mở `/hooks`, review và trust hook hash |
| GitHub Copilot Chat | Không | Cài CASAN VSIX và gửi `@casan <prompt>` |
| Claude Code CLI | Có | Mở repository dưới dạng trusted project |
| Claude Code trong VS Code / JetBrains | Có | Dùng project `.claude/settings.json`; mở trusted project |
| Codex desktop app — Local | Có | Mở `/hooks`, review và trust hook hash |
| Codex CLI / IDE extension — Local | Có | Mở `/hooks`, review và trust hook hash |
| Codex Cloud / Web | Chưa | Project hook local không phải cloud enforcement boundary |
| Claude Desktop / claude.ai | Chưa | Không chạy Claude Code project hooks |
| GitHub Copilot Chat | Không tự động | Cài CASAN VSIX và gửi `@casan <prompt>` |
`--client claude` cấu hình **Claude Code runtime**, dùng chung cho CLI, VS Code
extension và JetBrains integration. `--client codex` cấu hình **Codex local
runtime**, dùng chung cho desktop app, CLI và IDE extension. CASAN không tạo
adapter trùng lặp theo từng UI; cùng một project hook và cùng một integrity pin
được dùng trên các local surface.
Các cách chọn client:
+1 -1
View File
@@ -1 +1 @@
1.0.1
1.0.2
+7 -3
View File
@@ -193,12 +193,16 @@ cấp có chủ đích bằng cách chạy lại `casan init --runtime vendored`
### Capability theo client
| Lựa chọn | Trải nghiệm | Bước trust/cài đặt bắt buộc |
| Lựa chọn | Local surface được hỗ trợ | Bước trust/cài đặt bắt buộc |
|---|---|---|
| `claude` | Claude Code CLI và extension chính thức dùng project hooks | Mở trusted project; hook chạy tự động |
| `codex` | Codex CLI và IDE extension dùng `.codex/hooks.json` | Mở `/hooks`, review và trust đúng hook hash |
| `claude` | Claude Code CLI, VS Code extension và JetBrains integration dùng chung project settings/hooks | Mở trusted project; hook chạy tự động |
| `codex` | Codex desktop app (Local), CLI và IDE extension (Local) dùng `.codex/hooks.json` | Mở `/hooks`, review và trust đúng hook hash |
| `vscode-copilot` | GitHub Copilot Chat qua route **explicit `@casan`** | Cài VSIX do init tạo/cài; dùng `@casan <prompt>` |
Không gắn badge project-hook cho Codex Cloud/Web, Claude Desktop hoặc
claude.ai. Các surface đó không chạy local project hook; muốn support phải có
remote/managed integration riêng và một qualification suite riêng.
GitHub Copilot Chat mặc định **không** có public API để CASAN intercept mọi
prompt. Chỉ route `@casan` là `casan_owned`; participant detection/built-in
Copilot không được quảng bá là certified.
@@ -36,7 +36,8 @@ Spike tập trung trả lời bốn câu:
| Claude Code managed/custom SDK | Có | Có | Có | SDK ResultMessage có usage/cost tốt hơn | **GO cho assurance cao hơn** |
| Codex project hooks | `UserPromptSubmit` | `PreToolUse`, `PostToolUse` | `Stop` | Cần prototype nguồn usage/cost | **GO sau Claude, có điều kiện** |
| Codex managed hooks | Có thể pin bằng policy/requirements | Có, theo coverage docs | Có | Như trên | **GO cho enterprise rollout** |
| Claude/Codex VS Code extension | Chưa black-box verify | Chưa verify | Chưa verify | Chưa verify | **CONDITIONAL** |
| Claude Code CLI / VS Code / JetBrains | Dùng chung Claude Code project settings | Dùng chung hook contract | Dùng chung hook contract | Như Claude Code project hooks | **SUPPORTED local surfaces** |
| Codex desktop / CLI / IDE — Local | Dùng chung Codex project hook layer | Dùng chung hook contract | Dùng chung hook contract | Như Codex project hooks | **SUPPORTED local surfaces; trust bắt buộc** |
| VS Code Chat Participant | Participant sở hữu request được route tới nó | Participant tự điều phối tools | Participant sở hữu response | Tự ghi được usage do model API trả về | **GO cho `@casan`, không phải global interceptor** |
| VS Code participant detection | Auto-route best effort | Như participant nếu được route | Có | Có | **Không đủ để cam kết mọi prompt** |
| Generic Copilot built-in chat | Không có API công khai để intercept toàn bộ prompt | Không có CASAN gate chung | Không | Không | **NO-GO cho tuyên bố transparent absolute** |
@@ -95,7 +96,7 @@ runtime event/telemetry của Codex. Nếu không có nguồn ổn định, reco
**Verdict Codex:** GO có điều kiện, bắt đầu sau khi Claude adapter dùng chung lifecycle
contract đã pass. Managed hook là đường khuyến nghị khi tổ chức yêu cầu enforcement.
### 4.3 VS Code, Claude/Codex extension và Copilot
### 4.3 Local UI surfaces và Copilot
VS Code Chat Participant API cho phép extension tạo participant như `@casan` và sở hữu toàn
bộ prompt được route tới participant đó. Participant detection có thể tự chọn participant
@@ -106,14 +107,20 @@ Language Model Chat Provider chỉ xử lý request khi model/provider đó đư
không phải interceptor cho mọi model/chat có sẵn. Enterprise policy còn có thể vô hiệu
provider kiểu BYOK.
Claude Code hoặc Codex extension có thể tái sử dụng CLI/project hooks, nhưng spike chưa có
black-box evidence trên máy hiện tại. Mỗi extension/version phải được test độc lập trước khi
gắn badge `verified`.
Claude Code settings được tài liệu chính thức xác nhận dùng chung giữa CLI và VS Code
extension; cùng settings precedence áp dụng cho CLI, VS Code và JetBrains. Codex official
surface matrix xác định Hook áp dụng cho desktop app, CLI và IDE extension. Vì vậy CASAN
dùng một adapter theo runtime, không fork adapter theo UI.
Qualification vẫn phải phân biệt `supported contract` với `tested build`: mỗi release nên
smoke-test ít nhất một build của từng UI family và không suy rộng local project hook sang
Codex Cloud/Web, Claude Desktop hoặc claude.ai.
**Verdict VS Code:**
- GO cho Claude Code local trên CLI, VS Code và JetBrains qua shared project settings;
- GO có trust gate cho Codex local trên desktop app, CLI và IDE extension;
- GO cho CASAN-owned `@casan` Chat Participant;
- CONDITIONAL cho native Claude/Codex extension tới khi black-box test pass;
- NO-GO cho tuyên bố CASAN tự động intercept mọi prompt built-in Copilot bằng public API.
## 5. Kiến trúc prototype được chọn
@@ -233,8 +240,9 @@ Hướng này khả thi và nên làm theo đúng thứ tự đã yêu cầu:
1. **Claude Code:** có đủ lifecycle hook để làm MVP; đây là spike/prototype đầu tiên.
2. **Codex:** API hook phù hợp, reuse common bridge; managed hooks đáng ưu tiên cho rollout
tổ chức.
3. **VS Code:** xác minh native Claude/Codex extension riêng; bổ sung `@casan` participant
cho đường CASAN-owned. Không hứa intercept toàn bộ Copilot built-in chat.
3. **Local UI surfaces:** dùng chung adapter theo runtime; smoke-test desktop/CLI/IDE trong
release qualification. Bổ sung `@casan` participant cho đường CASAN-owned và không hứa
intercept toàn bộ Copilot built-in chat.
Kết quả được gọi là “đi qua CASAN” khi có trace/admission/finalize và certification strength
rõ ràng, không chỉ vì project có một file hướng dẫn hoặc hook telemetry.
@@ -242,6 +250,10 @@ rõ ràng, không chỉ vì project có một file hướng dẫn hoặc hook te
## 11. Nguồn chính thức
- [Claude Code hooks](https://code.claude.com/docs/en/hooks)
- [Claude Code IDE integrations](https://code.claude.com/docs/en/ide-integrations)
- [Claude Code settings](https://code.claude.com/docs/en/settings)
- [Codex hooks](https://learn.chatgpt.com/docs/hooks)
- [Codex surface glossary](https://learn.chatgpt.com/docs/glossary)
- [Claude Code status line](https://code.claude.com/docs/en/statusline)
- [Claude Agent SDK cost tracking](https://code.claude.com/docs/en/agent-sdk/cost-tracking)
- [Codex hooks](https://learn.chatgpt.com/docs/hooks)
+48 -7
View File
@@ -60,10 +60,35 @@ VSCODE_EXTENSION_IDS = {
PROMPT_MARKER_START = "<!-- CASAN_PROMPT_ENFORCEMENT_START -->"
PROMPT_MARKER_END = "<!-- CASAN_PROMPT_ENFORCEMENT_END -->"
CLIENT_LABELS = {
"claude": "Claude Code",
"codex": "Codex",
"claude": "Claude Code (CLI + VS Code + JetBrains)",
"codex": "Codex local (desktop + CLI + IDE)",
"vscode-copilot": "VS Code / @casan",
}
CLIENT_SURFACES = {
"claude": {
"supported_local": [
"claude-code-cli",
"claude-code-vscode",
"claude-code-jetbrains",
],
"not_covered": ["claude-desktop", "claude-web"],
"contract": "shared_claude_code_project_settings",
},
"codex": {
"supported_local": [
"codex-desktop-local",
"codex-cli",
"codex-ide-extension-local",
],
"not_covered": ["codex-cloud", "codex-web"],
"contract": "shared_codex_local_project_hooks",
},
"vscode-copilot": {
"supported_local": ["vscode-copilot-explicit-at-casan"],
"not_covered": ["github-copilot-built-in-chat"],
"contract": "casan_owned_explicit_route",
},
}
RUNTIME_MODES = ("managed", "vendored")
@@ -131,7 +156,7 @@ def _render_init(result):
print(_color("1", "Next steps"))
print(" 1. Run `casan doctor`")
if "codex" in result["clients"]:
print(" 2. In Codex, open `/hooks` and trust this project's hook.")
print(" 2. In a local Codex client, open `/hooks` and trust this project's hook.")
def _render_verify(result):
@@ -173,6 +198,9 @@ def _render_doctor(result):
for client, item in result["client_checks"].items():
label = CLIENT_LABELS.get(client, client)
print(" %s %s" % (_mark(item.get("ready", False)), label))
surfaces = item.get("supported_surfaces") or []
if surfaces:
print(" Surfaces: %s" % ", ".join(surfaces))
reason = item.get("smoke", {}).get("reason")
if reason and not item.get("ready"):
print(" %s" % reason)
@@ -903,8 +931,8 @@ def select_clients(values, interactive):
while True:
sys.stderr.write(
"\nClient integrations\n\n"
" 1) Claude Code (CLI + official VS Code extension)\n"
" 2) Codex (CLI + official VS Code extension)\n"
" 1) Claude Code (CLI + VS Code + JetBrains)\n"
" 2) Codex local (desktop app + CLI + IDE extension)\n"
" 3) GitHub Copilot in VS Code via explicit @casan route\n\n"
"Select clients (comma-separated) [1,2]: ")
sys.stderr.flush()
@@ -1108,7 +1136,7 @@ def cmd_init(args):
# ── .casan/config.json ──
cfg_dir = os.path.join(target, ".casan")
cfg = {
"schema_version": "21.1",
"schema_version": "21.2",
"project_id": project,
"created_at": now_iso(),
"enforcement_mode": args.mode,
@@ -1121,6 +1149,13 @@ def cmd_init(args):
if "vscode-copilot" in clients else "disabled",
"vscode-native-copilot": "unsupported_global_interception",
},
"client_surfaces": {
client: {
"enabled": client in clients,
**surface,
}
for client, surface in CLIENT_SURFACES.items()
},
"harness_version": version,
"adoption_model": (
"managed-global" if runtime_mode == "managed"
@@ -1511,7 +1546,13 @@ def cmd_doctor(args):
line.strip().lower() for line in listed.stdout.splitlines() if line.strip()}
for client in clients:
item = {"configured": True}
surface = CLIENT_SURFACES.get(client, {})
item = {
"configured": True,
"supported_surfaces": list(surface.get("supported_local", [])),
"not_covered_surfaces": list(surface.get("not_covered", [])),
"surface_contract": surface.get("contract"),
}
if client == "claude":
commands = _commands_in_hooks(
os.path.join(target, ".claude", "settings.json"))
@@ -6,6 +6,10 @@ installer does this). It wires the four Claude Code lifecycle hooks
bridge so a developer can type prompts normally while every certified turn still
carries an H1→H7 trace and an H6 record.
Claude Code shares these project settings across its CLI, official VS Code
extension, and JetBrains integration. Claude Desktop and claude.ai are separate
products and are not covered by this local project-hook integration.
- The hook command resolves the repo root via `$CLAUDE_PROJECT_DIR`, so **no
machine-specific path is committed**.
- It invokes `.casan/casan-hook.py`. That bootstrap reads project config,
@@ -5,6 +5,11 @@ handler calls `.casan/casan-hook.py`, which resolves and verifies the pinned
global harness before dispatching the Codex adapter. No custom `[casan]` TOML
keys are required.
This one integration covers the local Codex runtime across the desktop app,
CLI, and IDE extension. It does not claim coverage for Codex Cloud/Web, where a
repo-local process and its `.casan/casan-hook.py` bootstrap are not the local
execution boundary.
Important Codex-specific facts (Spike-20 §4.2):
- **Trust review is mandatory.** Codex loads project-local hooks only after the
@@ -151,6 +151,23 @@ PROJ="$WORK/proj/my-app"; mkdir -p "$PROJ"; echo '{"name":"x"}' > "$PROJ/package
for f in .casan/config.json .casan/version.lock .casan/agentic.env .casan/casan-hook.py .claude/settings.json .codex/hooks.json .vscode/extensions.json .specify/.gitignore; do
[[ -f "$PROJ/$f" ]] && pass "init wrote $f" || fail "init missing $f"
done
python3 - "$PROJ/.casan/config.json" <<'PY' \
&& pass "config declares supported and excluded client surfaces" \
|| fail "config client surface contract is incomplete"
import json
import sys
d = json.load(open(sys.argv[1], encoding="utf-8"))
assert d["schema_version"] == "21.2"
assert d["client_surfaces"]["claude"]["supported_local"] == [
"claude-code-cli", "claude-code-vscode", "claude-code-jetbrains",
]
assert d["client_surfaces"]["codex"]["supported_local"] == [
"codex-desktop-local", "codex-cli", "codex-ide-extension-local",
]
assert "claude-desktop" in d["client_surfaces"]["claude"]["not_covered"]
assert "codex-cloud" in d["client_surfaces"]["codex"]["not_covered"]
PY
if [[ -d "$PROJ/packages/casan-harness" ]]; then fail "harness was copied into the repo (should not be)"; else pass "harness NOT copied into repo (hybrid model)"; fi
if grep -R -q 'packages/casan-harness/adapters' "$PROJ/.claude/settings.json" "$PROJ/.codex/hooks.json"; then fail "generated hooks still target a repo-local harness"; else pass "generated hooks target the stable project bootstrap"; fi
@@ -6,8 +6,8 @@ S="$CASAN_HARNESS_ROOT/scripts/bash/upgrade-compatibility.sh"
M="$CASAN_APP_ROOT/infra/production/compatibility-matrix.json"
PASS=0; FAIL=0
pass(){ echo "PASS: $1"; PASS=$((PASS+1)); }; fail(){ echo "FAIL: $1"; FAIL=$((FAIL+1)); }
if bash "$S" check --from 1.0.0 --to 1.0.1 --matrix "$M" | grep -q '"decision": "allow"'; then pass "patch upgrade is explicitly allowed"; else fail "patch upgrade denied"; fi
if bash "$S" check --from 1.0.1 --to 1.0.0 --rollback --matrix "$M" | grep -q '"rollback": true'; then pass "approved rollback is explicitly allowed"; else fail "approved rollback denied"; fi
if bash "$S" check --from 1.0.1 --to 2.0.0 --matrix "$M" >/dev/null 2>&1; then fail "unreviewed major upgrade accepted"; else pass "unreviewed major upgrade denied"; fi
if bash "$S" check --from 1.0.1 --to 1.0.2 --matrix "$M" | grep -q '"decision": "allow"'; then pass "patch upgrade is explicitly allowed"; else fail "patch upgrade denied"; fi
if bash "$S" check --from 1.0.2 --to 1.0.1 --rollback --matrix "$M" | grep -q '"rollback": true'; then pass "approved rollback is explicitly allowed"; else fail "approved rollback denied"; fi
if bash "$S" check --from 1.0.2 --to 2.0.0 --matrix "$M" >/dev/null 2>&1; then fail "unreviewed major upgrade accepted"; else pass "unreviewed major upgrade denied"; fi
if bash "$S" check --from invalid --to 1.0.0 --matrix "$M" >/dev/null 2>&1; then fail "invalid version accepted"; else pass "invalid version denied"; fi
echo "===== UPGRADE COMPATIBILITY SUMMARY: PASS=$PASS FAIL=$FAIL ====="; [[ "$FAIL" -eq 0 ]]