feat: Claude Code context injection (UserPromptSubmit hook)
Closes the asymmetry the user surfaced: before this, Claude Code captured every turn (Stop hook) but retrieval only happened when Claude chose to call atocore_context (opt-in MCP tool). OpenClaw had both sides covered after 7I; Claude Code did not. Now symmetric. Every Claude Code prompt is auto-sent to /context/build and the returned pack is prepended via hookSpecificOutput.additionalContext — same as what OpenClaw's before_agent_start hook now does. - deploy/hooks/inject_context.py — UserPromptSubmit hook. Fail-open (always exit 0). Skips short/XML prompts. 5s timeout. Project inference mirrors capture_stop.py cwd→slug table. Kill switch: ATOCORE_CONTEXT_DISABLED=1. - ~/.claude/settings.json registered the hook (local config, not committed; copy-paste snippet in docs/capture-surfaces.md). - Removed /wiki/capture from topnav. Endpoint still exists but the page is now labeled "fallback only" with a warning banner. The sanctioned surfaces are Claude Code + OpenClaw; manual paste is explicitly not the design. - docs/capture-surfaces.md — scope statement: two surfaces, nothing else. Anthropic API polling explicitly prohibited. Tests: +8 for inject_context.py (exit 0 on all failure modes, kill switch, short prompt filter, XML filter, bad stdin, mock-server success shape, project inference from cwd). Updated 2 wiki tests for the topnav change. 450 → 459. Verified live with real AtoCore: injected 2979 chars of atocore project context on a cwd-matched prompt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,17 +30,23 @@ def _init_all():
|
||||
init_engineering_schema()
|
||||
|
||||
|
||||
def test_capture_page_renders(tmp_data_dir):
|
||||
def test_capture_page_renders_as_fallback(tmp_data_dir):
|
||||
_init_all()
|
||||
html = render_capture()
|
||||
assert "Capture a conversation" in html
|
||||
# Page is reachable but now labeled as a fallback, not promoted
|
||||
assert "fallback only" in html
|
||||
assert "sanctioned capture surfaces are Claude Code" in html
|
||||
# Form inputs still exist for emergency use
|
||||
assert "cap-prompt" in html
|
||||
assert "cap-response" in html
|
||||
# Topnav present
|
||||
assert "topnav" in html
|
||||
# Source options for mobile/desktop
|
||||
assert "claude-desktop" in html
|
||||
assert "claude-mobile" in html
|
||||
|
||||
|
||||
def test_capture_not_in_topnav(tmp_data_dir):
|
||||
"""The paste form should NOT appear in topnav — it's not the sanctioned path."""
|
||||
_init_all()
|
||||
html = render_homepage()
|
||||
assert "/wiki/capture" not in html
|
||||
assert "📥 Capture" not in html
|
||||
|
||||
|
||||
def test_memory_detail_renders(tmp_data_dir):
|
||||
@@ -125,12 +131,11 @@ def test_homepage_has_topnav_and_activity(tmp_data_dir):
|
||||
_init_all()
|
||||
create_memory("knowledge", "homepage test")
|
||||
html = render_homepage()
|
||||
# Topnav with expected items
|
||||
# Topnav with expected items (Capture removed — it's not sanctioned capture)
|
||||
assert "🏠 Home" in html
|
||||
assert "📡 Activity" in html
|
||||
assert "📥 Capture" in html
|
||||
assert "/wiki/capture" in html
|
||||
assert "/wiki/activity" in html
|
||||
assert "/wiki/capture" not in html
|
||||
# Activity snippet
|
||||
assert "What the brain is doing" in html
|
||||
|
||||
|
||||
Reference in New Issue
Block a user