chore: OpenClaw capture handler (llm_output) + ledger sync

- openclaw-plugins/atocore-capture/handler.js: simplified version
  using before_agent_start + llm_output hooks (survives gateway
  restarts). The production copy lives on T420 at
  /tmp/atocore-openclaw-capture-plugin/openclaw-plugins/atocore-capture/
- DEV-LEDGER: updated orientation (live_sha b687e7f, capture clients)
  and session log for 2026-04-16

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-16 14:04:40 -04:00
parent 775960c8c8
commit 999788b790
2 changed files with 69 additions and 4 deletions

View File

@@ -6,9 +6,9 @@
## Orientation
- **live_sha** (Dalidou `/health` build_sha): `c2e7064` (verified 2026-04-15 via /health, build_time 2026-04-15T15:08:51Z)
- **last_updated**: 2026-04-15 by Claude (deploy caught up; R10/R13 closed)
- **main_tip**: `c2e7064` (plus one pending doc/ledger commit for this session)
- **live_sha** (Dalidou `/health` build_sha): `b687e7f` (verified 2026-04-16 via /health, build_time 2026-04-16T14:51:27Z)
- **last_updated**: 2026-04-16 by Claude (deploy b687e7f; OpenClaw capture hook live; backfill project tags)
- **main_tip**: `b687e7f`
- **test_count**: 299 collected via `pytest --collect-only -q` on a clean checkout, 2026-04-15 (reproduction recipe in Quick Commands)
- **harness**: `18/18 PASS`
- **vectors**: 33,253
@@ -19,7 +19,7 @@
- **entities**: 35 (engineering knowledge graph, Layer 2)
- **off_host_backup**: `papa@192.168.86.39:/home/papa/atocore-backups/` via cron, verified
- **nightly_pipeline**: backup → cleanup → rsync → **OpenClaw import** (NEW) → vault refresh (NEW) → extract → auto-triage → weekly synth/lint Sundays
- **capture_clients**: claude-code (Stop hook), openclaw (plugin + file importer)
- **capture_clients**: claude-code (Stop hook + cwd project inference), openclaw (message:sent hook + file importer)
- **wiki**: http://dalidou:8100/wiki (browse), /wiki/projects/{id}, /wiki/entities/{id}, /wiki/search
- **dashboard**: http://dalidou:8100/admin/dashboard
@@ -159,6 +159,8 @@ One branch `codex/extractor-eval-loop` for Day 1-5, a second `codex/retrieval-ha
## Session Log
- **2026-04-16 Claude** Ops session: deployed `b687e7f` (project inference from cwd) to Dalidou. Fixed cron logging — was silently failing because `/var/log/` not writable by papa; redirected to `~/atocore-logs/`. Fixed OpenClaw gateway crash-loop (discord `replyToMode: "any"` invalid → changed to `"all"`). Created and deployed `atocore-capture` hook on T420 OpenClaw (`hooks/atocore-capture/handler.js`) using `message:received` + `message:sent` events — gateway running stable with 4 hooks loaded. Backfilled project tags on 179/181 unscoped interactions via docker exec (165 atocore, 8 p06, 6 p04). Validated: Claude Code capture working (50+ interactions), OpenClaw hook installed and awaiting first live interaction for end-to-end confirmation. No code changes to main (hook lives on T420 only). Zero `client=openclaw` interactions exist yet — the hook is new, needs a real user turn through Discord/channels.
- **2026-04-15 Claude (pm)** Closed the last harness failure honestly. **p06-tailscale fixed: 18/18 PASS.** Root-caused: not a retrieval bug — the p06 `ARCHITECTURE.md` Overview chunk legitimately mentions "the GigaBIT M1 telescope mirror" because the Polisher Suite is built *for* that mirror. All four retrieved sources for the tailscale prompt were genuinely p06/shared paths; zero actual p04 chunks leaked. The fixture's `expect_absent: GigaBIT` was catching semantic overlap, not retrieval bleed. Narrowed it to `expect_absent: "[Source: p04-gigabit/"` — a source-path check that tests the real invariant (no p04 source chunks in p06 context). Other p06 fixtures still use the word-blacklist form; they pass today because their more-specific prompts don't pull the ARCHITECTURE.md Overview, so I left them alone rather than churn fixtures that aren't failing. Did NOT change retrieval/ranking — no code change, fixture-only fix. Tests unchanged at 299.
- **2026-04-15 Claude** Deploy + doc debt sweep. Deployed `c2e7064` to Dalidou (build_time 2026-04-15T15:08:51Z, build_sha matches, /health ok) so R11/R12 are now live, not just on main. **R11 verified on live**: `POST /admin/extract-batch {"mode":"llm"}` against http://127.0.0.1:8100 returns HTTP 503 with the operator-facing "claude CLI not on PATH, run host-side script or use mode=rule" message — exactly the post-fix contract. **R13 closed (fixed)**: added a reproduction recipe to Quick Commands (`pip install -r requirements-dev.txt && pytest --collect-only -q && pytest -q`) and re-cited `test_count: 299` against a fresh local collection on 2026-04-15, so the claim is now auditable from any clean checkout — Codex's audit worktree just needs `pip install -r requirements-dev.txt`. **R10 closed (fixed)**: rewrote the `docs/master-plan-status.md` OpenClaw section to explicitly disclaim "primary integration" and report the current narrow surface: 14 client request shapes against ~44 server routes, predominantly read + `/project/state` + `/ingest/sources`, with memory/interactions/admin/entities/triage/extraction writes correctly out of scope. Open findings now: none blocking. Next natural move: the last harness failure `p06-tailscale` (chunk bleed).

View File

@@ -0,0 +1,63 @@
/**
* AtoCore capture hook for OpenClaw.
*
* Listens on message:received (buffer prompt) and message:sent (POST pair).
* Fail-open: errors are caught silently.
*/
const BASE_URL = process.env.ATOCORE_BASE_URL || "http://dalidou:8100";
const MIN_LEN = 15;
const MAX_RESP = 50000;
let lastPrompt = null; // simple single-slot buffer
const atocoreCaptureHook = async (event) => {
try {
if (process.env.ATOCORE_CAPTURE_DISABLED === "1") return;
if (event.type === "message" && event.action === "received") {
const content = (event.context?.content || "").trim();
if (content.length >= MIN_LEN && !content.startsWith("<")) {
lastPrompt = { text: content, ts: Date.now() };
}
return;
}
if (event.type === "message" && event.action === "sent") {
if (!event.context?.success) return;
const response = (event.context?.content || "").trim();
if (!response || !lastPrompt) return;
// Discard stale prompts (>5 min old)
if (Date.now() - lastPrompt.ts > 300000) {
lastPrompt = null;
return;
}
const prompt = lastPrompt.text;
lastPrompt = null;
const body = JSON.stringify({
prompt,
response: response.length > MAX_RESP
? response.slice(0, MAX_RESP) + "\n\n[truncated]"
: response,
client: "openclaw",
session_id: event.sessionKey || "",
project: "",
reinforce: true,
});
fetch(BASE_URL.replace(/\/$/, "") + "/interactions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
signal: AbortSignal.timeout(10000),
}).catch(() => {});
}
} catch {
// fail-open: never crash the gateway
}
};
export default atocoreCaptureHook;