Skip to content

feat(openclaw): replace TS integration with claude-smart-style openclaw-smart plugin#80

Open
yilu331 wants to merge 53 commits into
mainfrom
feat/openclaw-smart-plugin
Open

feat(openclaw): replace TS integration with claude-smart-style openclaw-smart plugin#80
yilu331 wants to merge 53 commits into
mainfrom
feat/openclaw-smart-plugin

Conversation

@yilu331
Copy link
Copy Markdown
Collaborator

@yilu331 yilu331 commented May 19, 2026

Summary

Replaces the prior TS-only reflexio/integrations/openclaw/plugin/ with a thin TS shim + Python openclaw_smart/ package that mirrors claude-smart 1:1.

  • New reflexio/server/llm/providers/openclaw_provider.py — LiteLLM CustomLLM that uses openClaw's CLI for extraction (analog of claude_code_provider.py).
  • Rewrites reflexio setup openclaw for the new plugin (plugin id reflexio-openclaw-smart, env writes for OPENCLAW_BIN + OPENCLAW_SMART_USE_LOCAL_CLI, --repair / --purge flags).
  • Feature parity with claude-smart: 6 hooks, 6 SKILL.md files, citation tracking ([oc:…]), secret masking, watermark-based publish resume, recursion guard.
  • Backend lives at http://localhost:8081/ (the reflexio default), distinct from claude-smart's 8071.

Spec & plan

Parent repo:

  • docs/superpowers/specs/2026-05-19-openclaw-smart-design.md
  • docs/superpowers/plans/2026-05-19-openclaw-smart.md

Test plan

  • Plugin tests: cd reflexio/integrations/openclaw/plugin && uv run pytest tests/ -o 'addopts=' → 136 passed, 2 skipped (live-backend tests)
  • TS shim: npx tsc --noEmit && npx vitest run → 5 passed
  • reflexio/server/llm/providers/openclaw_provider: 23 unit + 3 integration tests pass
  • reflexio setup openclaw tests: 6 unit tests pass (plugin id constant, env writes, install argv)
  • Manual end-to-end smoke test (reflexio setup openclaw → real openClaw session → observe publish + inject)

Companion parent-repo PR with docs + submodule bump will follow.

Summary by CodeRabbit

  • New Features

    • openclaw-smart integration: local Reflexio backend, new CLI commands (install/repair/uninstall), and five skills: learn, show, dashboard, restart, clear-all
  • Improvements

    • Streamlined setup/first-run installer with clearer completion/next-steps and safer clear-all behavior
    • Robust session buffering, publish/retry flow, citation-aware context injection, and optional local OpenClaw CLI provider with recursion guard
  • Tests

    • Expanded unit and integration tests for hooks, state, publish, CLI, and provider behaviors

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the OpenClaw federated plugin with an "openclaw-smart" TS shim and Python package, adds shell tooling and installer flows, updates the setup CLI to use a user-global .env, provides a local LiteLLM OpenClaw provider, and adds comprehensive tests for hooks, state, publish, and provider wiring.

Changes

OpenClaw Smart Integration

Layer / File(s) Summary
CLI setup & installer
reflexio/cli/commands/setup_cmd.py, reflexio/integrations/openclaw/plugin/scripts/npm-cli.js
Adds env-aware openclaw-smart install/uninstall/repair flows that write/remove OPENCLAW_* keys, run smart-install.sh, manage plugin install/enable/config, optionally purge state, and verify plugin load status.
Shell scripts & helpers
reflexio/integrations/openclaw/plugin/scripts/*, .../_lib.sh
Adds shared shell helpers, bootstrap/install (smart-install.sh), hook entry (hook_entry.sh), CLI wrapper (cli.sh), backend lifecycle manager (backend-service.sh), log runner, ensure-plugin-root, dashboard opener, and dashboard/build helpers.
TS shim plugin
reflexio/integrations/openclaw/plugin/index.ts, openclaw.plugin.json, package.json, tsconfig.*, vitest.config.ts
Replaces federated plugin with reflexio-openclaw-smart shim that forwards selected hooks to hook_entry.sh, updates plugin metadata/configSchema (search.top_k→3, extraction.model), and adjusts build/test config.
Python plugin package
reflexio/integrations/openclaw/plugin/src/openclaw_smart/*, pyproject.toml
New openclaw_smart package: hook dispatcher, event handlers (session_start, before_prompt_build, before_tool_call, after_tool_call, agent_end, session_end), context formatting/injection, oc_cite, optimizer assistant, ids/runtime, state JSONL persistence, publish orchestration, Adapter to Reflexio, and CLI (openclaw-smart).
Event handlers & state
.../events/*.py, .../state.py, .../publish.py
Buffers User/Assistant/Assistant_tool records, persists injected citation registries, folds tool records into Assistant turns, computes unpublished slices, and serializes publish with per-session locks.
Optimizer & query helpers
.../optimizer_assistant.py, .../query_compose.py
Adds local optimizer assistant wrapper calling openclaw infer and deterministic query composition for tool calls.
LiteLLM provider
reflexio/server/llm/providers/openclaw_provider.py, litellm_client.py
Adds opt-in OpenClaw CLI-backed LiteLLM provider that shells out to openclaw infer model run --json, extracts completion text, and registers when enabled/resolvable.
Tests & configs
reflexio/integrations/openclaw/plugin/tests/**, tests/server/llm/*, tests/cli/test_setup_cmd.py, tests-ts/*
Extensive pytest and Vitest suites covering hook dispatch, state persistence, publish, context injection, event handlers, optimizer assistant, provider behavior, and CLI setup tests.

Sequence Diagram — Hook dispatch and publish flow:

sequenceDiagram
  participant OpenClaw
  participant TS_Shim as TS Shim
  participant HookShell as hook_entry.sh
  participant PyHook as openclaw_smart.hook
  participant Adapter as Reflexio Adapter
  participant Reflexio as Reflexio Backend
  OpenClaw->>TS_Shim: lifecycle hook event
  TS_Shim->>HookShell: spawn hook_entry.sh with token + JSON
  HookShell->>PyHook: uv run -m openclaw_smart.hook (reads stdin)
  PyHook->>Adapter: search_all / publish_unpublished
  Adapter->>Reflexio: HTTP publish/search
  Reflexio-->>Adapter: search/publish responses
  PyHook-->>HookShell: emits prependContext or no-op (stdout)
  HookShell-->>TS_Shim: process stdout -> return to OpenClaw
Loading

Estimated code review effort:
🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs:

"A rabbit wrote this patch with hops and care,
Scripts and hooks nested everywhere.
A shim that passes JSON through shell and py,
Tests that prove the sessions live and fly.
🐇✨"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/openclaw-smart-plugin

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (6)
reflexio/integrations/openclaw/README.md-60-70 (1)

60-70: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the skills count mismatch in the section header.

The text says “Five user-invocable skills,” but the table currently lists six skills. Please align the count with the listed items.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/README.md` around lines 60 - 70, Update the
section header that currently reads “Five user-invocable skills” to match the
actual list under plugin/skills/ (either change it to “Six user-invocable
skills” or remove an entry from the table); ensure the header and the table of
skill names (`reflexio`, `learn`, `show`, `dashboard`, `restart`, `clear-all`)
are consistent so the count matches the listed items.
reflexio/integrations/openclaw/plugin/index.ts-82-86 (1)

82-86: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard reflexio_publish when no session key is known.

If the tool is called before any hook sets activeSessionKey, Line 85 passes an empty session id to CLI. Add an early return with a clear message.

Suggested fix
       async execute(_id, _params) {
         try {
+          if (!activeSessionKey) {
+            return {
+              content: [{ type: "text" as const, text: "publish skipped: no active session key yet" }],
+            };
+          }
           const r = await runner(
             ["bash", CLI_SCRIPT, "learn", "--session", activeSessionKey],
             { timeoutMs: 30000 },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/index.ts` around lines 82 - 86, The
execute method calls runner with CLI_SCRIPT and activeSessionKey even if
activeSessionKey is empty; add an early guard at the start of async execute(_id,
_params) that checks if activeSessionKey is falsy and returns immediately with a
clear message (or throws a controlled error) indicating no session key is set,
so runner([... "learn", "--session", activeSessionKey], ...) is never invoked
with an empty session; reference execute, activeSessionKey, runner and
CLI_SCRIPT when making the change.
reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md-8-8 (1)

8-8: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid hardcoding the dashboard URL in the user-facing response.

The opener script allows REFLEXIO_DASHBOARD_URL, so this message can be incorrect on non-default setups.

Proposed fix
-Then tell the user: "Dashboard at http://localhost:3001".
+Then tell the user: "Dashboard opened."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md` at line 8,
Replace the hardcoded string "Dashboard at http://localhost:3001" in SKILL.md
with a dynamic reference to the configured dashboard URL
(REFLEXIO_DASHBOARD_URL) so non-default setups show the correct link; update the
user-facing message to use the environment/config value (e.g.,
REFLEXIO_DASHBOARD_URL or its template placeholder) instead of the literal
http://localhost:3001, ensuring any runtime code or template that renders this
message reads REFLEXIO_DASHBOARD_URL and falls back to the default only if the
env var is unset.
reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md-12-12 (1)

12-12: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify the “degrade silently” rule to allow explicit install-failure guidance.

This line conflicts with the hook behavior that intentionally injects setup remediation on install-failure. Tightening wording here avoids mixed instructions.

Proposed fix
-4. **Degrade silently.** If the plugin is unavailable, proceed normally. Do not surface plumbing errors.
+4. **Degrade silently for transient runtime issues.** If explicit install/setup remediation is injected, follow it once; otherwise proceed normally without plumbing details.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md` at line 12,
Update the "Degrade silently" rule in SKILL.md to clarify that runtime failures
must not surface plumbing errors but that install-time failures may present
explicit setup/remediation guidance injected by the install-failure hook;
specifically replace or amend the sentence "Degrade silently. If the plugin is
unavailable, proceed normally. Do not surface plumbing errors." to something
like "Degrade silently at runtime; however, on install-failure the plugin (via
the install-failure hook) may surface explicit setup/remediation guidance—do not
expose low-level plumbing errors." Ensure the doc references the phrase "Degrade
silently" and the "install-failure hook" so readers understand the exception.
reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py-216-235 (1)

216-235: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden unpublished_slice against malformed record shapes.

Line 217 and Line 234 assume published_up_to is numeric and tool_input is mapping-shaped. JSON-valid but schema-invalid lines can raise (idx < published, .items()), breaking publish retries.

Proposed fix
         if "published_up_to" in rec:
-            published = rec["published_up_to"]
+            marker = rec.get("published_up_to")
+            if isinstance(marker, int) and marker >= 0:
+                published = marker
             pending_tools = []
             turns = []
             continue
@@
-            tool_input = rec.get("tool_input") or {}
+            raw_tool_input = rec.get("tool_input")
+            tool_input = raw_tool_input if isinstance(raw_tool_input, dict) else {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py` around
lines 216 - 235, The code assumes rec["published_up_to"] is numeric and
rec.get("tool_input") is a mapping; harden unpublished_slice by validating
types: when seeing "published_up_to" on rec, coerce/validate it to an int (or
set published = -1/None and skip numeric comparisons) and guard the subsequent
idx < published comparison against non-numeric published; likewise, before
iterating tool_input.items() in the Assistant_tool branch, ensure tool_input is
a mapping (isinstance(tool_input, dict) or similar) and fall back to an empty
dict or safe-string representation when malformed so .items() won't raise —
update references in this function to use these validated/preserved values (rec,
published, tool_input, tool_entry) and short-circuit or sanitize malformed
records.
reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py-30-33 (1)

30-33: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace hardcoded /tmp with tmp_path in test payload.

Line 32 uses a hardcoded temp path, which triggers Ruff S108 and is less portable.

Proposed fix
-def test_handle_applies_extraction_defaults(fake_adapter, capsys):
+def test_handle_applies_extraction_defaults(fake_adapter, capsys, tmp_path):
     with patch.object(session_start, "_adapter", return_value=fake_adapter):
-        session_start.handle({"sessionKey": "s1", "workspaceDir": "/tmp"})
+        session_start.handle({"sessionKey": "s1", "workspaceDir": str(tmp_path)})
     fake_adapter.apply_extraction_defaults.assert_called_once_with(
         window_size=5, stride_size=3
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py`
around lines 30 - 33, The test test_handle_applies_extraction_defaults currently
uses a hardcoded "/tmp" for the workspaceDir; change the test to accept the
pytest tmp_path fixture and pass a portable path (e.g. str(tmp_path) or
tmp_path.as_posix()) into the payload when calling
session_start.handle({"sessionKey": "s1", "workspaceDir": ...}); update the test
signature to include tmp_path and keep the rest (patch.object(session_start,
"_adapter", ...), fake_adapter assertions) unchanged.
🧹 Nitpick comments (2)
reflexio/integrations/openclaw/plugin/scripts/_lib.sh (1)

321-333: ⚡ Quick win

Localize function variables to avoid global scope leakage.

In Line 322 and Line 366, variables are assigned without local. Since this file is sourced, these names can leak and clobber caller state.

Suggested fix
 openclaw_smart_kill_tree() {
-  pid="$1"
+  local pid target current_pgid candidates ours remaining cmdline
+  pid="$1"
   [ -z "$pid" ] && return 0
   if openclaw_smart_is_windows; then
@@
-    target=""
+    target=""
 openclaw_smart_pid_alive_file() {
-  pid_file="$1"
+  local pid_file pid
+  pid_file="$1"
   [ -f "$pid_file" ] || return 1
   pid=$(cat "$pid_file" 2>/dev/null || echo "")

Also applies to: 365-369

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/scripts/_lib.sh` around lines 321 -
333, The function openclaw_smart_kill_tree declares pid and target without
local, leaking them into the caller; make pid local (e.g., local pid="$1") and
declare local target and any temporary variables (e.g., local p child pid_list)
used in the function so they don't clobber the environment. Also apply the same
fix to the nearby function(s) that assign variables without local around the
365-369 area — find those unprefixed variables and prefix them with local to
fully localize all temporary names.
reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py (1)

30-37: ⚡ Quick win

Assert a single publish call in the fallback-path test.

Line 36 reads call_args directly; this won’t catch accidental multiple invocations. Add assert_called_once() and validate the same key flags you assert in the sessionKey test.

Proposed test hardening
 def test_handle_falls_back_to_session_id_key():
     with patch("openclaw_smart.events.session_end.publish") as pub, patch(
         "openclaw_smart.events.session_end.ids.resolve_project_id_with_fallback",
         return_value="proj-y",
     ):
         session_end.handle({"sessionId": "s2"})
+        pub.publish_unpublished.assert_called_once()
         kwargs = pub.publish_unpublished.call_args[1]
         assert kwargs["session_id"] == "s2"
+        assert kwargs["project_id"] == "proj-y"
+        assert kwargs["force_extraction"] is True
+        assert kwargs["skip_aggregation"] is False
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py`
around lines 30 - 37, In test_handle_falls_back_to_session_id_key(), ensure the
publish path is only invoked once and that the published kwargs include the same
key flags as the sessionKey test: add
pub.publish_unpublished.assert_called_once() after the call, then read the
single call's kwargs (pub.publish_unpublished.call_args[1]) and assert
kwargs["session_id"] == "s2" plus the same key-flag assertions you used in the
sessionKey test to validate the publish payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/cli/commands/setup_cmd.py`:
- Around line 633-648: The code validates openclaw_bin but later calls
"openclaw" directly in multiple subprocess.run calls, creating a TOCTOU risk;
update every subprocess invocation that currently uses the literal "openclaw"
(look for calls near where openclaw_bin is computed and functions like
_write_openclaw_env and references to _OPENCLAW_PLUGIN_ID and plugin_dir are
nearby) to use the resolved openclaw_bin variable instead (preserve argument
order and check flags like "--force" and plugin id), ensuring you pass the
absolute path rather than the string "openclaw".

In `@reflexio/integrations/openclaw/plugin/index.ts`:
- Around line 10-12: PLUGIN_ROOT uses __dirname which is undefined in ESM;
replace that by constructing a directory from import.meta.url (e.g., use
fileURLToPath(import.meta.url) and path.dirname) and then set PLUGIN_ROOT to
that result so HOOK_ENTRY and CLI_SCRIPT (the constants PLUGIN_ROOT, HOOK_ENTRY,
CLI_SCRIPT) reference the correct runtime directory; import fileURLToPath from
"url" if not present and update the constants to use the computed directory.

In `@reflexio/integrations/openclaw/plugin/scripts/cli.sh`:
- Around line 47-50: The script currently uses set -e which causes the whole
script to exit if OPENCLAW_SMART_BOOTSTRAPPING=1 bash
"$PLUGIN_ROOT/scripts/smart-install.sh" fails, preventing the subsequent
fallback checks (and calls to openclaw_smart_prepend_astral_bins and
openclaw_smart_prepend_node_bins) from running; change the call to
smart-install.sh so its failure is captured instead of short-circuiting the
script (e.g., temporarily disable errexit around the invocation or run in a
subshell and save its exit code), then restore set -e and branch on that saved
exit code to execute the existing structured fallback/messages; apply the same
pattern to the similar block covering lines 51–60.

In `@reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh`:
- Around line 65-93: After running the inline bootstrap (when "$EVENT" =
"session-start" or when spawning smart-install.sh inline), re-check for the
install-failed marker and abort before proceeding: after invoking
OPENCLAW_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" (and
after any smart-prepend calls) test for the presence of
"$STATE_DIR/install-failed" (or whatever install-failed sentinel the installer
writes) and if it exists exit 0 (or skip running further
hooks/uv/backend-service.sh); also keep the existing uv presence checks but
ensure the install-failed check runs before starting backend-service.sh or
allowing the flow to continue to uv run.

In `@reflexio/integrations/openclaw/plugin/scripts/smart-install.sh`:
- Around line 47-53: The flock branch in smart-install.sh currently prints a
warning then calls exit 0 when flock 9 fails, which causes the install to abort;
remove the exit so the script falls back to the intended lockfile path and
continues execution. Specifically, in the block that checks command -v flock and
opens exec 9>"$INSTALL_LOCK", change the if ! flock 9; then branch to only emit
the stderr warning (echo "[openclaw-smart] install lock failed; continuing
without serialization" >&2 and optional empty echo) and do not call exit; keep
the rest of the script flow (ensuring exec 9 remains closed later or is ignored)
so installation proceeds when flock cannot be acquired.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py`:
- Around line 519-527: The current try/except around the clear-all loop returns
early on exception (in the block iterating over targets calling
_remove_clear_all_target), which prevents the backend restart; change the error
handling to log the exception to sys.stderr, set a failure flag (e.g.,
clear_all_failed = True) and continue/exit the loop without returning so that
the subsequent backend restart logic still runs; after the loop, ensure the
restart routine is invoked unconditionally and then exit with a non-zero code if
clear_all_failed is True (apply the same pattern to the similar block at the
other location).
- Around line 156-160: The subprocess.run(...) calls in openclaw_smart/cli.py
currently only catch subprocess.CalledProcessError, so OSError (e.g., file not
found or permission denied) will crash; update both try/except blocks around
subprocess.run to also catch OSError (e.g., except
(subprocess.CalledProcessError, OSError) as exc) and return a controlled status
(use exc.errno if present or fall back to 1) instead of letting the process
crash; apply the same change to the second subprocess.run call referenced in the
file.

In
`@reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py`:
- Around line 127-128: The code currently assigns "tool_input" from
payload.get("tool_input") or payload.get("params") (which may be non-dict) and
later passes it to _redact, causing exceptions; update the after-tool-call
handler to validate and coerce the tool_input before redaction: check
payload.get("tool_input") and payload.get("params") and only pass a dict into
_redact (e.g., if value is None use {}, if it's not a dict convert to {"value":
str(value)} or drop to {}), and apply the same guard for the other occurrence
that sets "tool_input" and "tool_response" so _redact is only ever invoked with
a dict. Ensure you reference and change the code paths that construct the dict
passed into _redact (the keys "tool_input" and "tool_response" in the
after_tool_call event handler).

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py`:
- Around line 29-43: The git toplevel resolution can still raise OSError and
depends on PATH; update the block in ids.py (the subprocess.run call that uses
["git", "rev-parse", "--show-toplevel"]) to first resolve the git executable
with shutil.which("git") and skip/return None if not found, then call
subprocess.run using the absolute git path, and expand the except to catch
OSError as well (i.e., except (FileNotFoundError, subprocess.TimeoutExpired,
OSError) as exc) so the function truly never raises due to executable lookup or
process spawn errors.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py`:
- Around line 35-38: Replace the brittle direct parent access for _REFLEXIO_DIR
with a robust resolution: keep honoring the OPENCLAW_SMART_REFLEXIO_DIR env var,
and if unset, walk _THIS_DIR.parents safely (e.g., for parent in
_THIS_DIR.parents with a bounded depth) looking for a distinguishing marker (a
"reflexio" package directory, pyproject.toml, or .git) and select that parent;
if nothing is found, fall back to a sensible default or raise a clear
RuntimeError instead of letting IndexError bubble up. Update the assignment of
_REFLEXIO_DIR in internal_call.py to implement this safe parent-walking logic
that references the _THIS_DIR and _REFLEXIO_DIR symbols so imports never fail
due to IndexError.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py`:
- Around line 57-72: The publish_unpublished flow can race and double-publish
because read_all -> publish -> append is not atomic; wrap the whole sequence in
a per-session lock (e.g., acquire a session-specific mutex keyed by session_id
or call a new state.lock_session/session_unlock API) so only one publisher runs
for that session at a time; acquire the lock before calling
state.read_all/state.unpublished_slice and hold it until after state.append (or
until publish fails), ensure the lock is released in a finally block, and do not
advance the watermark (state.append) unless client.publish returns success;
reference state.read_all, state.unpublished_slice, client.publish (Adapter), and
state.append when implementing this change.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py`:
- Around line 42-52: Both _from_file_edit and _from_bash assume payload fields
are strings and may crash when given non-strings; update _from_file_edit (fields
"new_string" and "content") and _from_bash (field "command") to defensively
validate or coerce values to str before performing slice/splitlines (e.g., check
isinstance(..., str) and else use "" or call str(...) safely), then proceed with
existing basename, slicing ([:_MAX_SNIPPET_LEN]) and splitlines()[0] logic so
malformed payloads return an empty string instead of raising.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py`:
- Around line 64-71: session_path and injected_path currently embed session_id
directly into filenames, allowing path-traversal; sanitize/validate session_id
before using it: ensure it contains only an allowed token format (e.g.
alphanumerics, dashes/underscores or a UUID regex) or otherwise canonicalize by
taking the final basename (Path(session_id).name) and rejecting values like "."
or ".."; if validation fails raise a ValueError. Update both
session_path(session_id: str) and injected_path(session_id: str) to perform this
check/canonicalization and then build the path using state_dir() /
safe_session_id.

In `@reflexio/server/llm/providers/openclaw_provider.py`:
- Around line 186-193: The code coerces None into the string "None" by using
str(kwargs.get("model", "")), preventing the ENV_DEFAULT_MODEL fallback and
possibly passing "--model None" to the CLI; fix by reading the raw value (e.g.,
model_kwarg = kwargs.get("model")), treat None or empty string as missing, then
if model_kwarg is a string check for "/" and split as currently done (use
model_kwarg.split("/",1)[1] or None), and finally set model =
os.environ.get(ENV_DEFAULT_MODEL) if model is falsy; ensure you do not call
str() on the original value so None stays None.
- Line 196: The code reads the timeout with timeout_s =
int(os.environ.get(ENV_TIMEOUT, str(_DEFAULT_TIMEOUT_SECONDS))) which can raise
ValueError if the env var is non-numeric; update the logic (e.g., in the
openclaw provider initialization where timeout_s is defined) to catch parsing
errors: try to parse int(os.environ.get(ENV_TIMEOUT, ...)) inside a try/except
(ValueError) and fall back to _DEFAULT_TIMEOUT_SECONDS, optionally logging a
warning via the provider logger; ensure ENV_TIMEOUT and _DEFAULT_TIMEOUT_SECONDS
remain the referenced symbols so the safe fallback is used instead of letting
the exception crash completion.

---

Minor comments:
In `@reflexio/integrations/openclaw/plugin/index.ts`:
- Around line 82-86: The execute method calls runner with CLI_SCRIPT and
activeSessionKey even if activeSessionKey is empty; add an early guard at the
start of async execute(_id, _params) that checks if activeSessionKey is falsy
and returns immediately with a clear message (or throws a controlled error)
indicating no session key is set, so runner([... "learn", "--session",
activeSessionKey], ...) is never invoked with an empty session; reference
execute, activeSessionKey, runner and CLI_SCRIPT when making the change.

In `@reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md`:
- Line 8: Replace the hardcoded string "Dashboard at http://localhost:3001" in
SKILL.md with a dynamic reference to the configured dashboard URL
(REFLEXIO_DASHBOARD_URL) so non-default setups show the correct link; update the
user-facing message to use the environment/config value (e.g.,
REFLEXIO_DASHBOARD_URL or its template placeholder) instead of the literal
http://localhost:3001, ensuring any runtime code or template that renders this
message reads REFLEXIO_DASHBOARD_URL and falls back to the default only if the
env var is unset.

In `@reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md`:
- Line 12: Update the "Degrade silently" rule in SKILL.md to clarify that
runtime failures must not surface plumbing errors but that install-time failures
may present explicit setup/remediation guidance injected by the install-failure
hook; specifically replace or amend the sentence "Degrade silently. If the
plugin is unavailable, proceed normally. Do not surface plumbing errors." to
something like "Degrade silently at runtime; however, on install-failure the
plugin (via the install-failure hook) may surface explicit setup/remediation
guidance—do not expose low-level plumbing errors." Ensure the doc references the
phrase "Degrade silently" and the "install-failure hook" so readers understand
the exception.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py`:
- Around line 216-235: The code assumes rec["published_up_to"] is numeric and
rec.get("tool_input") is a mapping; harden unpublished_slice by validating
types: when seeing "published_up_to" on rec, coerce/validate it to an int (or
set published = -1/None and skip numeric comparisons) and guard the subsequent
idx < published comparison against non-numeric published; likewise, before
iterating tool_input.items() in the Assistant_tool branch, ensure tool_input is
a mapping (isinstance(tool_input, dict) or similar) and fall back to an empty
dict or safe-string representation when malformed so .items() won't raise —
update references in this function to use these validated/preserved values (rec,
published, tool_input, tool_entry) and short-circuit or sanitize malformed
records.

In `@reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py`:
- Around line 30-33: The test test_handle_applies_extraction_defaults currently
uses a hardcoded "/tmp" for the workspaceDir; change the test to accept the
pytest tmp_path fixture and pass a portable path (e.g. str(tmp_path) or
tmp_path.as_posix()) into the payload when calling
session_start.handle({"sessionKey": "s1", "workspaceDir": ...}); update the test
signature to include tmp_path and keep the rest (patch.object(session_start,
"_adapter", ...), fake_adapter assertions) unchanged.

In `@reflexio/integrations/openclaw/README.md`:
- Around line 60-70: Update the section header that currently reads “Five
user-invocable skills” to match the actual list under plugin/skills/ (either
change it to “Six user-invocable skills” or remove an entry from the table);
ensure the header and the table of skill names (`reflexio`, `learn`, `show`,
`dashboard`, `restart`, `clear-all`) are consistent so the count matches the
listed items.

---

Nitpick comments:
In `@reflexio/integrations/openclaw/plugin/scripts/_lib.sh`:
- Around line 321-333: The function openclaw_smart_kill_tree declares pid and
target without local, leaking them into the caller; make pid local (e.g., local
pid="$1") and declare local target and any temporary variables (e.g., local p
child pid_list) used in the function so they don't clobber the environment. Also
apply the same fix to the nearby function(s) that assign variables without local
around the 365-369 area — find those unprefixed variables and prefix them with
local to fully localize all temporary names.

In `@reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py`:
- Around line 30-37: In test_handle_falls_back_to_session_id_key(), ensure the
publish path is only invoked once and that the published kwargs include the same
key flags as the sessionKey test: add
pub.publish_unpublished.assert_called_once() after the call, then read the
single call's kwargs (pub.publish_unpublished.call_args[1]) and assert
kwargs["session_id"] == "s2" plus the same key-flag assertions you used in the
sessionKey test to validate the publish payload.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8bba5dad-2ccd-4f4b-9dab-2479dc79144e

📥 Commits

Reviewing files that changed from the base of the PR and between 9d0f839 and a50658c.

⛔ Files ignored due to path filters (2)
  • reflexio/integrations/openclaw/package-lock.json is excluded by !**/package-lock.json
  • reflexio/integrations/openclaw/plugin/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (120)
  • reflexio/cli/README.md
  • reflexio/cli/app.py
  • reflexio/cli/commands/embeddings.py
  • reflexio/cli/commands/services.py
  • reflexio/cli/commands/setup_cmd.py
  • reflexio/cli/run_services.py
  • reflexio/cli/stop_services.py
  • reflexio/cli/utils.py
  • reflexio/integrations/claude_code/README.md
  • reflexio/integrations/claude_code/hook/HOOK.md
  • reflexio/integrations/claude_code/hook/handler.js
  • reflexio/integrations/claude_code/hook/search_hook.js
  • reflexio/integrations/claude_code/hook/session_start_hook.sh
  • reflexio/integrations/openclaw/README.md
  • reflexio/integrations/openclaw/TESTING.md
  • reflexio/integrations/openclaw/hook/handler.js
  • reflexio/integrations/openclaw/package.json
  • reflexio/integrations/openclaw/plugin/.gitignore
  • reflexio/integrations/openclaw/plugin/README.md
  • reflexio/integrations/openclaw/plugin/hook/handler.ts
  • reflexio/integrations/openclaw/plugin/hook/setup.ts
  • reflexio/integrations/openclaw/plugin/index.ts
  • reflexio/integrations/openclaw/plugin/lib/publish.ts
  • reflexio/integrations/openclaw/plugin/lib/search.ts
  • reflexio/integrations/openclaw/plugin/lib/server.ts
  • reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts
  • reflexio/integrations/openclaw/plugin/lib/user-id.ts
  • reflexio/integrations/openclaw/plugin/openclaw.plugin.json
  • reflexio/integrations/openclaw/plugin/package.json
  • reflexio/integrations/openclaw/plugin/pyproject.toml
  • reflexio/integrations/openclaw/plugin/rules/reflexio.md
  • reflexio/integrations/openclaw/plugin/scripts/_lib.sh
  • reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh
  • reflexio/integrations/openclaw/plugin/scripts/backend-service.sh
  • reflexio/integrations/openclaw/plugin/scripts/cli.sh
  • reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh
  • reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh
  • reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh
  • reflexio/integrations/openclaw/plugin/scripts/smart-install.sh
  • reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/show/SKILL.md
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/__init__.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py
  • reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts
  • reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts
  • reflexio/integrations/openclaw/plugin/tests/__init__.py
  • reflexio/integrations/openclaw/plugin/tests/integration/__init__.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py
  • reflexio/integrations/openclaw/plugin/tests/test_cli.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py
  • reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py
  • reflexio/integrations/openclaw/plugin/tests/test_ids.py
  • reflexio/integrations/openclaw/plugin/tests/test_internal_call.py
  • reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py
  • reflexio/integrations/openclaw/plugin/tests/test_query_compose.py
  • reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py
  • reflexio/integrations/openclaw/plugin/tests/test_runtime.py
  • reflexio/integrations/openclaw/plugin/tests/test_state.py
  • reflexio/integrations/openclaw/plugin/tsconfig.json
  • reflexio/integrations/openclaw/plugin/types/openclaw.d.ts
  • reflexio/integrations/openclaw/plugin/vitest.config.ts
  • reflexio/integrations/openclaw/publish_clawhub.sh
  • reflexio/integrations/openclaw/references/HOOK.md
  • reflexio/integrations/openclaw/scripts/install.sh
  • reflexio/integrations/openclaw/scripts/uninstall.sh
  • reflexio/integrations/openclaw/tests/publish.test.ts
  • reflexio/integrations/openclaw/tests/search.test.ts
  • reflexio/integrations/openclaw/tests/server.test.ts
  • reflexio/integrations/openclaw/tests/setup.test.ts
  • reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts
  • reflexio/integrations/openclaw/tests/user-id.test.ts
  • reflexio/integrations/openclaw/vitest.config.ts
  • reflexio/server/llm/embedding_service.py
  • reflexio/server/llm/litellm_client.py
  • reflexio/server/llm/providers/embedding_service_provider.py
  • reflexio/server/llm/providers/local_embedding_provider.py
  • reflexio/server/llm/providers/nomic_embedding_provider.py
  • reflexio/server/llm/providers/openclaw_provider.py
  • reflexio/server/services/storage/sqlite_storage/_base.py
  • reflexio/server/services/storage/sqlite_storage/_profiles.py
  • tests/cli/test_embeddings_cmd.py
  • tests/cli/test_service_builders.py
  • tests/cli/test_services_first_run.py
  • tests/cli/test_setup_cmd.py
  • tests/server/llm/test_embedding_service_provider.py
  • tests/server/llm/test_openclaw_provider.py
  • tests/server/llm/test_openclaw_provider_integration.py
  • tests/server/services/storage/test_sqlite_storage.py
💤 Files with no reviewable changes (22)
  • reflexio/integrations/openclaw/publish_clawhub.sh
  • reflexio/integrations/openclaw/scripts/uninstall.sh
  • reflexio/integrations/openclaw/tests/publish.test.ts
  • reflexio/integrations/openclaw/tests/search.test.ts
  • reflexio/integrations/openclaw/vitest.config.ts
  • reflexio/integrations/openclaw/references/HOOK.md
  • reflexio/integrations/openclaw/tests/server.test.ts
  • reflexio/integrations/openclaw/plugin/lib/user-id.ts
  • reflexio/integrations/openclaw/tests/user-id.test.ts
  • reflexio/integrations/openclaw/hook/handler.js
  • reflexio/integrations/openclaw/plugin/lib/search.ts
  • reflexio/integrations/openclaw/package.json
  • reflexio/integrations/openclaw/scripts/install.sh
  • reflexio/integrations/openclaw/plugin/hook/handler.ts
  • reflexio/integrations/openclaw/tests/setup.test.ts
  • reflexio/integrations/openclaw/plugin/lib/server.ts
  • reflexio/integrations/openclaw/plugin/lib/publish.ts
  • reflexio/integrations/openclaw/plugin/rules/reflexio.md
  • reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts
  • reflexio/integrations/openclaw/TESTING.md
  • reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts
  • reflexio/integrations/openclaw/plugin/hook/setup.ts

Comment thread reflexio/cli/commands/setup_cmd.py
Comment thread reflexio/integrations/openclaw/plugin/index.ts Outdated
Comment thread reflexio/integrations/openclaw/plugin/scripts/cli.sh Outdated
Comment thread reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh
Comment thread reflexio/integrations/openclaw/plugin/scripts/smart-install.sh
Comment thread reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py Outdated
Comment thread reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py Outdated
Comment thread reflexio/server/llm/providers/openclaw_provider.py Outdated
Comment thread reflexio/server/llm/providers/openclaw_provider.py Outdated
yilu331 added a commit that referenced this pull request May 19, 2026
Addresses 12 major findings from CodeRabbit on the openclaw-smart PR:

* state.py — sanitize session_id before constructing filesystem paths
  to prevent path traversal via crafted session keys (e.g. '../escape').
  Rejected ids cause append/read calls to silently no-op.
* openclaw_provider.py — guard against kwargs.get('model') being None
  (was being coerced to literal 'None') and against an invalid
  OPENCLAW_CLI_TIMEOUT env var crashing the completion path.
* setup_cmd.py — resolve openclaw CLI to its absolute path once and use
  it for every subprocess call, avoiding mid-setup TOCTOU drift.
* ids.py — resolve git binary via shutil.which() and catch generic
  OSError so resolve_project_id() truly never raises.
* internal_call.py — guard the parents[5] indexing so an unfamiliar
  install layout (PyPI wheel, alternate cache path) doesn't crash on
  module import.
* after_tool_call.py — wrap non-dict tool_input in a {'_raw': ...} dict
  so malformed payloads can't trip _redact()'s dict assumption.
* query_compose.py — coerce non-string tool payload fields through
  _as_str() so an unexpected payload type can't crash hook execution.
* cli.py (plugin) — catch OSError from subprocess.run() so missing
  exec-bit / permission-denied surfaces as a controlled status code.
* hook_entry.sh — re-check install-failed marker after inline
  bootstrap; smart-install.sh can fail post-uv-install (e.g. uv sync).
* cli.sh — don't let smart-install.sh's non-zero exit short-circuit
  set -e past the structured failure-marker checks.
* smart-install.sh — flock failure falls back to lockfile path instead
  of bailing out and silently skipping the install.
* README.md — fix 'Five user-invocable skills' header to match the
  actual six entries in the skills table.

Updated test_install_openclaw_uses_new_plugin_id and added
test_path_traversal_session_id_rejected / test_safe_session_ids_accepted
regression coverage. Plugin tests: 138 passed, 2 skipped.

Skipped per scope:
* publish.py per-session lock (heavy lift; spec §10 already documents
  the multi-session limitation)
* cli.py clear-all backend-restart refactor (invasive; safe failure
  path is already a controlled 'failed' status)
@yilu331 yilu331 force-pushed the feat/openclaw-smart-plugin branch from a99a8f3 to 408ec34 Compare May 19, 2026 04:40
@yilu331
Copy link
Copy Markdown
Collaborator Author

yilu331 commented May 19, 2026

Force-pushed 408ec34 (rebased on the latest main so the merge conflict is gone, plus a single commit that addresses 12 of the 15 CodeRabbit major findings).

Addressed

# File Fix
1 setup_cmd.py Resolve openclaw_bin to abs path once and use it for all 7 subprocess calls (TOCTOU)
2 index.ts __dirnamefileURLToPath(import.meta.url) (was already fixed in the previous push)
3 cli.sh || echo … around bootstrap so set -e doesn't skip the failure-marker checks
4 hook_entry.sh Re-check install-failed marker after inline bootstrap
5 smart-install.sh flock failure now falls back to lockfile path instead of exiting
6 cli.py (plugin) Catch OSError from subprocess.run so missing exec-bit returns a controlled status
8 events/after_tool_call.py Wrap non-dict tool_input in {"_raw": ...} to keep _redact happy
9 ids.py shutil.which("git") + broad OSError so _resolve_from_git truly never raises
10 internal_call.py Guard parents[5] indexing for unfamiliar install layouts
12 query_compose.py _as_str() helper to coerce non-string tool payload fields
13 state.py Path traversal fix: sanitize session_id against [A-Za-z0-9._-]{1,128} + reject .., with test_path_traversal_session_id_rejected regression coverage
14 openclaw_provider.py Guard kwargs.get("model") against None"None" coercion
15 openclaw_provider.py Guard OPENCLAW_CLI_TIMEOUT ValueError with fallback to default

Minor: README "Five user-invocable skills" → "Six skill folders (one always-on contract, five user-invocable)".

Deferred (out of scope for this PR)

Test status

  • Plugin tests: 138 passed, 2 skipped (the 2 skips are live-backend tests).
  • TS shim tests: 5 passed.
  • setup_cmd + openclaw_provider tests in tests/: 83 passed.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (2)
reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py (1)

526-549: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure backend restart still runs when wipe fails mid-operation.

Line 533 returns immediately on deletion error, so a previously running backend can remain stopped. This is the same restart-gap previously reported and still present.

Proposed fix
-    removed_targets = 0
-    try:
+    removed_targets = 0
+    clear_failed = False
+    try:
         for target in targets:
             if _remove_clear_all_target(target):
                 removed_targets += 1
     except (OSError, _ClearAllError) as exc:
         sys.stderr.write(f"error: could not remove reflexio data: {exc}\n")
-        return 1
+        clear_failed = True
@@
     start_rc = 0
     if was_running:
         sys.stdout.write("Starting reflexio backend…\n")
         start_rc = _run_service(_BACKEND_SCRIPT, "start")
+    if clear_failed:
+        return start_rc or 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py` around lines
526 - 549, The current wipe loop aborts early on an OSError/_ClearAllError
(returning 1) which prevents the later restart logic (_run_service with
_BACKEND_SCRIPT when was_running is True) from running; change the error
handling around the targets loop so exceptions are logged to stderr and a
failure flag/rc is set (e.g., rc = 1) instead of returning immediately, continue
to process removed_buffers and always execute the start block that calls
_run_service when was_running is True, and ensure the final exit code reflects
any failures encountered during deletion.
reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh (1)

100-103: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Surface install-failure context for session-start after inline bootstrap.

The second failure-marker gate returns an empty payload for all events. When install fails during this same invocation, session-start loses the actionable error text that the earlier gate already knows how to emit.

Proposed fix
 if [ -f "$FAILURE_MARKER" ]; then
-  echo ''
+  if [ "$EVENT" = "session-start" ] && command -v python3 >/dev/null 2>&1; then
+    python3 - "$FAILURE_MARKER" <<'PY'
+import json, pathlib, sys
+msg = pathlib.Path(sys.argv[1]).read_text().strip() or "unknown error"
+print(json.dumps({
+    "prependContext": (
+        f"> **openclaw-smart is not installed correctly:** {msg}\n"
+        "> Re-run the plugin's Setup (restart openClaw) "
+        "or fix the underlying issue and delete "
+        "`~/.openclaw-smart/install-failed` to retry."
+    )
+}))
+PY
+  else
+    echo ''
+  fi
   exit 0
 fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh` around lines 100
- 103, The second FAILURE_MARKER gate in hook_entry.sh currently prints an empty
payload (echo '') which discards the actionable install-failure text for
downstream session-start events; change this branch to surface the earlier
failure context by reading and emitting the stored failure content (from the
FAILURE_MARKER file or the same variable used by the earlier gate) instead of
printing an empty string, then exit as before—look for the FAILURE_MARKER check
in hook_entry.sh and update the session-start/failure gate to output the
marker's contents so the earlier error is preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/cli/commands/setup_cmd.py`:
- Around line 818-824: Add validation before the current conditional block to
enforce mutually exclusive flags: if both repair and uninstall are set,
raise/exit with a clear error message; if purge is set without uninstall,
raise/exit with a clear error message. Update logic around the existing
_repair_openclaw() and _uninstall_openclaw(env_path=env_path, purge=purge) calls
so you first validate the flag combinations, then call _repair_openclaw() when
only repair is true or call _uninstall_openclaw(...) when uninstall is true
(allowing purge only in that branch).

In `@reflexio/integrations/openclaw/plugin/scripts/backend-service.sh`:
- Around line 50-59: The fallback currently resolves the OpenClaw binary but
exports OPENCLAW_SMART_CLI_PATH instead of the expected OPENCLAW_BIN; change the
exports in the resolution block so the resolved path is stored in OPENCLAW_BIN
(and optionally also set OPENCLAW_SMART_CLI_PATH as an alias for backward
compatibility). Update the export statements in the backend-service.sh
resolution logic (the code that sets _oc_cli_path via command -v and checks
$HOME/.local/bin/openclaw) to export OPENCLAW_BIN="resolved_path" (and if you
want compatibility, also export OPENCLAW_SMART_CLI_PATH="$OPENCLAW_BIN").

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py`:
- Around line 262-283: The code assumes rec["published_up_to"] is an int and
that rec["tool_input"] is a dict/rec["tool_output"] is a string; validate types
before using them: when handling the "published_up_to" key ensure its value is
an int (or coerce/skip if malformed) before assigning to published, and in the
Assistant_tool branch check that tool_input is an instance of dict before
iterating (otherwise treat it as empty dict) and ensure tool_output is a
str-like value (or skip/truncate safely) before calling
_truncate_tool_data_field; update the logic around published, rec, tool_input,
tool_output and use isinstance checks (or safe fallbacks) so malformed/legacy
JSONL rows don't raise at runtime.

In
`@reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py`:
- Around line 3-5: The test helper defaults REFLEXIO_URL to
"http://localhost:8071/" which mismatches openclaw-smart's default port; update
the default to "http://localhost:8081/" wherever REFLEXIO_URL is set or
referenced (including the helper used by
test_publish_to_local_reflexio_integration.py and the other occurrences around
lines 18-20) so the integration tests target the correct backend port; ensure
any related docstring/comment in that helper is updated to reflect the new
default.

In
`@reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py`:
- Around line 26-27: The helper function _reflexio_url currently defaults
REFLEXIO_URL to "http://localhost:8071/" which is the wrong port; change the
default to OpenClaw's backend port "http://localhost:8081/". Update the return
value in _reflexio_url() so os.environ.get("REFLEXIO_URL",
"http://localhost:8081/") is used, ensuring live integration tests use the
correct local backend when REFLEXIO_URL is unset.

---

Duplicate comments:
In `@reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh`:
- Around line 100-103: The second FAILURE_MARKER gate in hook_entry.sh currently
prints an empty payload (echo '') which discards the actionable install-failure
text for downstream session-start events; change this branch to surface the
earlier failure context by reading and emitting the stored failure content (from
the FAILURE_MARKER file or the same variable used by the earlier gate) instead
of printing an empty string, then exit as before—look for the FAILURE_MARKER
check in hook_entry.sh and update the session-start/failure gate to output the
marker's contents so the earlier error is preserved.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py`:
- Around line 526-549: The current wipe loop aborts early on an
OSError/_ClearAllError (returning 1) which prevents the later restart logic
(_run_service with _BACKEND_SCRIPT when was_running is True) from running;
change the error handling around the targets loop so exceptions are logged to
stderr and a failure flag/rc is set (e.g., rc = 1) instead of returning
immediately, continue to process removed_buffers and always execute the start
block that calls _run_service when was_running is True, and ensure the final
exit code reflects any failures encountered during deletion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e55454f-2df4-44e9-a81c-34d06c36c9be

📥 Commits

Reviewing files that changed from the base of the PR and between a50658c and f1a5e2b.

⛔ Files ignored due to path filters (2)
  • reflexio/integrations/openclaw/package-lock.json is excluded by !**/package-lock.json
  • reflexio/integrations/openclaw/plugin/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (98)
  • reflexio/cli/commands/setup_cmd.py
  • reflexio/integrations/openclaw/README.md
  • reflexio/integrations/openclaw/TESTING.md
  • reflexio/integrations/openclaw/hook/handler.js
  • reflexio/integrations/openclaw/package.json
  • reflexio/integrations/openclaw/plugin/.gitignore
  • reflexio/integrations/openclaw/plugin/README.md
  • reflexio/integrations/openclaw/plugin/hook/handler.ts
  • reflexio/integrations/openclaw/plugin/hook/setup.ts
  • reflexio/integrations/openclaw/plugin/index.ts
  • reflexio/integrations/openclaw/plugin/lib/publish.ts
  • reflexio/integrations/openclaw/plugin/lib/search.ts
  • reflexio/integrations/openclaw/plugin/lib/server.ts
  • reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts
  • reflexio/integrations/openclaw/plugin/lib/user-id.ts
  • reflexio/integrations/openclaw/plugin/openclaw.plugin.json
  • reflexio/integrations/openclaw/plugin/package.json
  • reflexio/integrations/openclaw/plugin/pyproject.toml
  • reflexio/integrations/openclaw/plugin/rules/reflexio.md
  • reflexio/integrations/openclaw/plugin/scripts/_lib.sh
  • reflexio/integrations/openclaw/plugin/scripts/backend-log-runner.sh
  • reflexio/integrations/openclaw/plugin/scripts/backend-service.sh
  • reflexio/integrations/openclaw/plugin/scripts/cli.sh
  • reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh
  • reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh
  • reflexio/integrations/openclaw/plugin/scripts/hook_entry.sh
  • reflexio/integrations/openclaw/plugin/scripts/smart-install.sh
  • reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/show/SKILL.md
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/__init__.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py
  • reflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.ts
  • reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts
  • reflexio/integrations/openclaw/plugin/tests/__init__.py
  • reflexio/integrations/openclaw/plugin/tests/integration/__init__.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py
  • reflexio/integrations/openclaw/plugin/tests/test_cli.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_session_start.py
  • reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py
  • reflexio/integrations/openclaw/plugin/tests/test_ids.py
  • reflexio/integrations/openclaw/plugin/tests/test_internal_call.py
  • reflexio/integrations/openclaw/plugin/tests/test_oc_cite.py
  • reflexio/integrations/openclaw/plugin/tests/test_query_compose.py
  • reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py
  • reflexio/integrations/openclaw/plugin/tests/test_runtime.py
  • reflexio/integrations/openclaw/plugin/tests/test_state.py
  • reflexio/integrations/openclaw/plugin/tsconfig.build.json
  • reflexio/integrations/openclaw/plugin/tsconfig.json
  • reflexio/integrations/openclaw/plugin/types/openclaw.d.ts
  • reflexio/integrations/openclaw/plugin/vitest.config.ts
  • reflexio/integrations/openclaw/publish_clawhub.sh
  • reflexio/integrations/openclaw/references/HOOK.md
  • reflexio/integrations/openclaw/scripts/install.sh
  • reflexio/integrations/openclaw/scripts/uninstall.sh
  • reflexio/integrations/openclaw/tests/publish.test.ts
  • reflexio/integrations/openclaw/tests/search.test.ts
  • reflexio/integrations/openclaw/tests/server.test.ts
  • reflexio/integrations/openclaw/tests/setup.test.ts
  • reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts
  • reflexio/integrations/openclaw/tests/user-id.test.ts
  • reflexio/integrations/openclaw/vitest.config.ts
  • reflexio/server/llm/litellm_client.py
  • reflexio/server/llm/providers/openclaw_provider.py
  • tests/cli/test_setup_cmd.py
  • tests/server/llm/test_openclaw_provider.py
  • tests/server/llm/test_openclaw_provider_integration.py
💤 Files with no reviewable changes (22)
  • reflexio/integrations/openclaw/tests/server.test.ts
  • reflexio/integrations/openclaw/tests/search.test.ts
  • reflexio/integrations/openclaw/package.json
  • reflexio/integrations/openclaw/references/HOOK.md
  • reflexio/integrations/openclaw/plugin/rules/reflexio.md
  • reflexio/integrations/openclaw/tests/publish.test.ts
  • reflexio/integrations/openclaw/scripts/install.sh
  • reflexio/integrations/openclaw/scripts/uninstall.sh
  • reflexio/integrations/openclaw/vitest.config.ts
  • reflexio/integrations/openclaw/plugin/lib/server.ts
  • reflexio/integrations/openclaw/plugin/hook/handler.ts
  • reflexio/integrations/openclaw/tests/user-id.test.ts
  • reflexio/integrations/openclaw/hook/handler.js
  • reflexio/integrations/openclaw/TESTING.md
  • reflexio/integrations/openclaw/publish_clawhub.sh
  • reflexio/integrations/openclaw/tests/setup.test.ts
  • reflexio/integrations/openclaw/plugin/lib/user-id.ts
  • reflexio/integrations/openclaw/plugin/lib/sqlite-buffer.ts
  • reflexio/integrations/openclaw/tests/sqlite-buffer.test.ts
  • reflexio/integrations/openclaw/plugin/hook/setup.ts
  • reflexio/integrations/openclaw/plugin/lib/publish.ts
  • reflexio/integrations/openclaw/plugin/lib/search.ts
✅ Files skipped from review due to trivial changes (8)
  • reflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.md
  • reflexio/integrations/openclaw/plugin/tsconfig.json
  • reflexio/integrations/openclaw/plugin/skills/restart/SKILL.md
  • reflexio/integrations/openclaw/plugin/.gitignore
  • reflexio/integrations/openclaw/plugin/skills/learn/SKILL.md
  • reflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.md
  • reflexio/integrations/openclaw/plugin/tests-ts/mocks/plugin-entry-stub.ts
  • reflexio/integrations/openclaw/plugin/skills/show/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (37)
  • reflexio/integrations/openclaw/plugin/tests/test_ids.py
  • reflexio/integrations/openclaw/plugin/vitest.config.ts
  • reflexio/integrations/openclaw/plugin/openclaw.plugin.json
  • reflexio/integrations/openclaw/plugin/tests/test_query_compose.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.py
  • reflexio/integrations/openclaw/plugin/scripts/dashboard-open.sh
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py
  • reflexio/integrations/openclaw/plugin/tests/test_cli.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.py
  • reflexio/integrations/openclaw/plugin/pyproject.toml
  • reflexio/integrations/openclaw/plugin/index.ts
  • reflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.py
  • reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.py
  • tests/server/llm/test_openclaw_provider_integration.py
  • reflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.py
  • reflexio/integrations/openclaw/plugin/tests/test_internal_call.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py
  • reflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.py
  • reflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.sh
  • reflexio/server/llm/providers/openclaw_provider.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_agent_end.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.py
  • tests/server/llm/test_openclaw_provider.py

Comment thread reflexio/cli/commands/setup_cmd.py
Comment thread reflexio/integrations/openclaw/plugin/scripts/backend-service.sh
Comment thread reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/cli/commands/setup_cmd.py`:
- Around line 674-690: The subprocess.run call that sets
f"plugins.entries.{_OPENCLAW_PLUGIN_ID}.hooks.allowConversationAccess" is
currently fire-and-forget; change it to check the result and fail setup if the
setting wasn't persisted by re-running a get or inspecting the run result (check
CompletedProcess.returncode and stdout/stderr) after the set; if returncode != 0
or the fetched value != "true", raise an exception or exit with a non-zero
status so setup does not print success while hooks will be blocked. Ensure you
update the code around the existing subprocess.run invocation (the call that
uses _OPENCLAW_PLUGIN_ID and the allowConversationAccess key) to perform this
validation and handle errors deterministically.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b6c6cba-796a-4fdf-9497-2aa845e89136

📥 Commits

Reviewing files that changed from the base of the PR and between f1a5e2b and f58bc2d.

📒 Files selected for processing (6)
  • reflexio/cli/commands/setup_cmd.py
  • reflexio/integrations/openclaw/plugin/index.ts
  • reflexio/integrations/openclaw/plugin/openclaw.plugin.json
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py
  • reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts
  • reflexio/integrations/openclaw/plugin/tests/test_state.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • reflexio/integrations/openclaw/plugin/openclaw.plugin.json
  • reflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.ts
  • reflexio/integrations/openclaw/plugin/tests/test_state.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py

Comment thread reflexio/cli/commands/setup_cmd.py
Comment thread reflexio/cli/commands/setup_cmd.py Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py`:
- Around line 275-281: The code that handles rec["published_up_to"] currently
accepts any non-negative int and resets published/pending_tools/turns; change it
to validate the marker against the current stream position before applying the
reset: read the marker from rec.get("published_up_to"), ensure it is an int and
that published <= marker <= current_stream_position (or the highest available
sequence/index for this stream) — only then set published = marker and clear
pending_tools and turns; otherwise ignore the marker and leave
published/pending_tools/turns untouched. Use the existing variables/names (rec,
published_up_to marker, published, pending_tools, turns, and whatever value
represents current_stream_position in this scope) to locate and implement the
check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 73c5f0b2-7827-4d28-9003-4d7f514c9ddf

📥 Commits

Reviewing files that changed from the base of the PR and between f58bc2d and b20e108.

📒 Files selected for processing (12)
  • reflexio/cli/commands/setup_cmd.py
  • reflexio/integrations/openclaw/plugin/scripts/backend-service.sh
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.py
  • reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.py
  • reflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.py
  • reflexio/integrations/openclaw/plugin/tests/test_cli.py
  • reflexio/integrations/openclaw/plugin/tests/test_events_session_end.py
  • reflexio/integrations/openclaw/plugin/tests/test_publish.py
  • reflexio/integrations/openclaw/plugin/tests/test_state.py
  • tests/cli/test_setup_cmd.py

Comment on lines +275 to +281
if "published_up_to" in rec:
marker = rec.get("published_up_to")
if isinstance(marker, int) and marker >= 0:
published = marker
pending_tools = []
turns = []
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ignore invalid/out-of-range published_up_to markers instead of applying reset logic.

A malformed integer marker (e.g., very large) is currently accepted and resets turns, which can skip valid unpublished records and delay/lose publishing. Only apply marker reset when the marker is valid for the current stream position.

💡 Suggested fix
     for idx, rec in enumerate(records):
         if "published_up_to" in rec:
             marker = rec.get("published_up_to")
-            if isinstance(marker, int) and marker >= 0:
+            if isinstance(marker, int) and 0 <= marker <= idx:
                 published = marker
-            pending_tools = []
-            turns = []
+                pending_tools = []
+                turns = []
             continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py` around
lines 275 - 281, The code that handles rec["published_up_to"] currently accepts
any non-negative int and resets published/pending_tools/turns; change it to
validate the marker against the current stream position before applying the
reset: read the marker from rec.get("published_up_to"), ensure it is an int and
that published <= marker <= current_stream_position (or the highest available
sequence/index for this stream) — only then set published = marker and clear
pending_tools and turns; otherwise ignore the marker and leave
published/pending_tools/turns untouched. Use the existing variables/names (rec,
published_up_to marker, published, pending_tools, turns, and whatever value
represents current_stream_position in this scope) to locate and implement the
check.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
reflexio/integrations/openclaw/README.md (1)

3-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Backend default port in docs conflicts with this PR’s stated behavior.

This section says openclaw-smart uses http://localhost:8071/ and matches claude-smart, but the PR objective states openclaw-smart defaults to http://localhost:8081 and is distinct. Please align the README to avoid misconfiguration.

Suggested wording
-into a local [Reflexio](https://github.com/reflexio-ai/reflexio) backend
-running at `http://localhost:8071/`. The port matches claude-smart so both
-plugins share one bundled reflexio (one SQLite store, one extractor), and
-leaves reflexio's own 8081 default free for developer use.
+into a local [Reflexio](https://github.com/reflexio-ai/reflexio) backend
+running at `http://localhost:8081/`. This differs from claude-smart’s 8071
+default.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/README.md` around lines 3 - 9, The README
currently states openclaw-smart points at http://localhost:8071 and shares the
bundled Reflexio instance with claude-smart; update the text so it correctly
reflects this PR: state that openclaw-smart defaults to http://localhost:8081
(Reflexio’s default) and is distinct from claude-smart (i.e., does not share the
same bundled Reflexio/SQLite instance), replacing the mention of 8071 and the
“matches claude-smart” wording; edit the paragraph referencing openclaw-smart,
claude-smart, and the Reflexio port to use 8081 and clarify separation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reflexio/integrations/openclaw/plugin/README.md`:
- Around line 36-38: Update the README entry describing the install command so
it documents both environment variables the installer writes: include
OPENCLAW_BIN and OPENCLAW_SMART_USE_LOCAL_CLI=1 in the `install` summary (the
section that currently mentions only OPENCLAW_BIN), and state that the installer
registers `reflexio-openclaw-smart` with OpenClaw, enables typed hook access,
writes both OPENCLAW_BIN and OPENCLAW_SMART_USE_LOCAL_CLI=1 to ~/.reflexio/.env,
warms Python dependencies, and verifies the plugin is loaded.

In `@reflexio/integrations/openclaw/plugin/scripts/npm-cli.js`:
- Around line 74-78: resolveOpenClawBin currently may return a relative path
(from OPENCLAW_BIN or findExecutable) which gets persisted and later fails when
cwd changes; update resolveOpenClawBin to always return an absolute path by
checking path.isAbsolute(result) and, if not, converting it with
path.resolve(process.cwd(), result) or using fs.realpathSync to get the
canonical absolute path before returning. Apply the same absolute-path
normalization to the other resolver at lines 146-149 so any value written to
~/.reflexio/.env is an absolute path.

---

Outside diff comments:
In `@reflexio/integrations/openclaw/README.md`:
- Around line 3-9: The README currently states openclaw-smart points at
http://localhost:8071 and shares the bundled Reflexio instance with
claude-smart; update the text so it correctly reflects this PR: state that
openclaw-smart defaults to http://localhost:8081 (Reflexio’s default) and is
distinct from claude-smart (i.e., does not share the same bundled
Reflexio/SQLite instance), replacing the mention of 8071 and the “matches
claude-smart” wording; edit the paragraph referencing openclaw-smart,
claude-smart, and the Reflexio port to use 8081 and clarify separation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5affcbe3-209a-4408-bd06-8871db8b42c5

📥 Commits

Reviewing files that changed from the base of the PR and between b20e108 and 9fbe317.

📒 Files selected for processing (7)
  • reflexio/integrations/openclaw/README.md
  • reflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.js
  • reflexio/integrations/openclaw/npm/openclaw-smart/package.json
  • reflexio/integrations/openclaw/plugin/README.md
  • reflexio/integrations/openclaw/plugin/package.json
  • reflexio/integrations/openclaw/plugin/scripts/npm-cli.js
  • reflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts

Comment on lines +36 to +38
`install` registers `reflexio-openclaw-smart` with OpenClaw, enables typed hook
access, writes `OPENCLAW_BIN` to `~/.reflexio/.env`, warms Python dependencies,
and verifies the plugin is loaded.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document both env vars written by install.

The command description mentions only OPENCLAW_BIN, but installer behavior also writes OPENCLAW_SMART_USE_LOCAL_CLI=1. Please include both for accuracy.

Suggested wording
-`install` registers `reflexio-openclaw-smart` with OpenClaw, enables typed hook
-access, writes `OPENCLAW_BIN` to `~/.reflexio/.env`, warms Python dependencies,
-and verifies the plugin is loaded.
+`install` registers `reflexio-openclaw-smart` with OpenClaw, enables typed hook
+access, writes `OPENCLAW_BIN` and `OPENCLAW_SMART_USE_LOCAL_CLI=1` to
+`~/.reflexio/.env`, warms Python dependencies, and verifies the plugin is loaded.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`install` registers `reflexio-openclaw-smart` with OpenClaw, enables typed hook
access, writes `OPENCLAW_BIN` to `~/.reflexio/.env`, warms Python dependencies,
and verifies the plugin is loaded.
`install` registers `reflexio-openclaw-smart` with OpenClaw, enables typed hook
access, writes `OPENCLAW_BIN` and `OPENCLAW_SMART_USE_LOCAL_CLI=1` to
`~/.reflexio/.env`, warms Python dependencies, and verifies the plugin is loaded.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/README.md` around lines 36 - 38, Update
the README entry describing the install command so it documents both environment
variables the installer writes: include OPENCLAW_BIN and
OPENCLAW_SMART_USE_LOCAL_CLI=1 in the `install` summary (the section that
currently mentions only OPENCLAW_BIN), and state that the installer registers
`reflexio-openclaw-smart` with OpenClaw, enables typed hook access, writes both
OPENCLAW_BIN and OPENCLAW_SMART_USE_LOCAL_CLI=1 to ~/.reflexio/.env, warms
Python dependencies, and verifies the plugin is loaded.

Comment on lines +74 to +78
function resolveOpenClawBin() {
const configured = process.env.OPENCLAW_BIN;
if (configured && existsSync(configured)) return configured;
return findExecutable("openclaw");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist OPENCLAW_BIN as an absolute path.

resolveOpenClawBin() can return a relative path, and that value is written to ~/.reflexio/.env. Later backend subprocesses may run from a different cwd and fail to locate the CLI.

Suggested fix
 function resolveOpenClawBin() {
   const configured = process.env.OPENCLAW_BIN;
-  if (configured && existsSync(configured)) return configured;
-  return findExecutable("openclaw");
+  if (configured && existsSync(configured)) return resolve(configured);
+  const discovered = findExecutable("openclaw");
+  return discovered ? resolve(discovered) : null;
 }

Also applies to: 146-149

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reflexio/integrations/openclaw/plugin/scripts/npm-cli.js` around lines 74 -
78, resolveOpenClawBin currently may return a relative path (from OPENCLAW_BIN
or findExecutable) which gets persisted and later fails when cwd changes; update
resolveOpenClawBin to always return an absolute path by checking
path.isAbsolute(result) and, if not, converting it with
path.resolve(process.cwd(), result) or using fs.realpathSync to get the
canonical absolute path before returning. Apply the same absolute-path
normalization to the other resolver at lines 146-149 so any value written to
~/.reflexio/.env is an absolute path.

yilu331 added 21 commits May 21, 2026 00:39
Extracts _resolve_from_git() helper so resolve_project_id (cwd-fallback)
and resolve_project_id_with_fallback (agent_id-fallback for openClaw,
which can run outside a git repo) share the git toplevel check.
Drops claude-code/codex-specific signals (CLAUDE_CODE_ENTRYPOINT,
Codex title/suggestion prompts) and uses the OPENCLAW_SMART_INTERNAL
env marker plus a workspaceDir-inside-reflexio check as the two
recursion-guard signals.
Renames CLAUDE_SMART_STATE_DIR→OPENCLAW_SMART_STATE_DIR and the default
buffer path ~/.claude-smart/sessions/ → ~/.openclaw-smart/sessions/.
cs-cite references renamed to oc-cite.
Adapter wraps ReflexioClient with port 8081 default. agent_version comes
from runtime.agent_version() which returns 'openclaw' so playbooks and
searches roll up across all openClaw projects.
Citation tags use [oc:s1-ab12] / [oc:p1-cd34] format. CITATION_INSTRUCTION
references 'openclaw-smart learning applied' marker line. Bin script
will live at plugin/bin/oc-cite (Phase 7).
Bullets render as '- [oc:s1-abc1] content' / '- [oc:p1-xyz7] content'.
Project header reads 'openclaw-smart — project `name`'.
Emits {"prependContext": markdown} envelope on stdout (per Phase 0
finding B2 — prependContext is per-turn, prependSystemContext is cached).
TS shim translates this to the openClaw plugin-SDK return shape.
Drops hook_event_name parameter; the TS shim knows the event.
Shells out to 'openclaw infer model run --prompt ... --json'. Unlike
claude/codex, openClaw's one-shot completion has no tool access, so the
optimizer evaluates rules purely from prompt context. Sets
OPENCLAW_SMART_INTERNAL=1 to prevent hook recursion.
Reads sessionKey (or sessionId), pushes extraction (5/3) and optimizer
defaults to reflexio, emits {"prependContext": banner} if a learning
stall is active. Falls back silently when nothing to inject. Adds
openclaw-smart-optimizer-assistant console script.
Reads sessionKey/sessionId + prompt + agentId/workspaceDir from payload,
buffers User turn to JSONL, calls context_inject.emit_context which
writes {"prependContext": markdown} to stdout. Per Phase 0 finding B2,
uses prependContext (per-turn) not prependSystemContext (cached).
yilu331 added 29 commits May 21, 2026 00:39
…=True

Diverges from claude-smart's session_end (force_extraction=False) so the
final session's lessons are extracted synchronously and visible in the
very next session.
Six events: session-start, before-prompt-build, before-tool-call,
after-tool-call, agent-end, session-end. Dispatcher reads stdin JSON,
routes to handler, and emits nothing on no-op paths (unknown event,
recursion guard, handler exception) so the TS shim sees empty stdout
as 'no return value'.
Implements show, learn, restart, clear-all. Drops claude-smart's
install/uninstall flows (Codex marketplace, etc.) — those go through
reflexio setup openclaw (Phase 10) and the openClaw plugin system.
OpenClaw 2026.5.12+ requires compiled JS at the path declared in
package.json's openclaw.extensions. Add a 'build' script that emits
./dist/index.js via tsc, point the manifest at it, and have
smart-install.sh run npm run build on first install. Switch index.ts to
import.meta.url-based path resolution since the compiled output is ESM
and __dirname is unavailable.

Verified locally: openclaw plugins install --link <plugin> +
openclaw plugins inspect reflexio-openclaw-smart → Status: loaded.
Addresses 12 major findings from CodeRabbit on the openclaw-smart PR:

* state.py — sanitize session_id before constructing filesystem paths
  to prevent path traversal via crafted session keys (e.g. '../escape').
  Rejected ids cause append/read calls to silently no-op.
* openclaw_provider.py — guard against kwargs.get('model') being None
  (was being coerced to literal 'None') and against an invalid
  OPENCLAW_CLI_TIMEOUT env var crashing the completion path.
* setup_cmd.py — resolve openclaw CLI to its absolute path once and use
  it for every subprocess call, avoiding mid-setup TOCTOU drift.
* ids.py — resolve git binary via shutil.which() and catch generic
  OSError so resolve_project_id() truly never raises.
* internal_call.py — guard the parents[5] indexing so an unfamiliar
  install layout (PyPI wheel, alternate cache path) doesn't crash on
  module import.
* after_tool_call.py — wrap non-dict tool_input in a {'_raw': ...} dict
  so malformed payloads can't trip _redact()'s dict assumption.
* query_compose.py — coerce non-string tool payload fields through
  _as_str() so an unexpected payload type can't crash hook execution.
* cli.py (plugin) — catch OSError from subprocess.run() so missing
  exec-bit / permission-denied surfaces as a controlled status code.
* hook_entry.sh — re-check install-failed marker after inline
  bootstrap; smart-install.sh can fail post-uv-install (e.g. uv sync).
* cli.sh — don't let smart-install.sh's non-zero exit short-circuit
  set -e past the structured failure-marker checks.
* smart-install.sh — flock failure falls back to lockfile path instead
  of bailing out and silently skipping the install.
* README.md — fix 'Five user-invocable skills' header to match the
  actual six entries in the skills table.

Updated test_install_openclaw_uses_new_plugin_id and added
test_path_traversal_session_id_rejected / test_safe_session_ids_accepted
regression coverage. Plugin tests: 138 passed, 2 skipped.

Skipped per scope:
* publish.py per-session lock (heavy lift; spec §10 already documents
  the multi-session limitation)
* cli.py clear-all backend-restart refactor (invasive; safe failure
  path is already a controlled 'failed' status)
openclaw-smart's bundled reflexio backend was binding to 8081, the
reflexio library default. That port is also what a developer running
`./run_services.sh` against the main repo uses, so installing the
plugin on a dev machine caused two failure modes:

* port_occupied check skipped autostart, leaving hooks unable to publish
* or the plugin silently attached to the dev backend, contaminating each
  other's data

Switch to 8071/8072 — literally the same ports claude-smart uses — so the
two plugins share one bundled backend (one SQLite, one extractor) and
8081 stays reserved for the developer's own instance.

Affects the adapter default URL, backend-service.sh PORT/EMBEDDING_PORT,
the integration test REFLEXIO_URL defaults, the README port reference,
and the unit-test assertion. The integration suite that previously
skipped (no backend reachable at 8081) now passes against the running
shared backend on 8071.
The plugin was failing to register on the openClaw gateway with:
  TypeError: Cannot read properties of undefined (reading 'runCommandWithTimeout')
and 'openclaw plugins doctor' explained the root cause:
  plugin must declare contracts.tools before registering agent tools

Two issues — both fixed here:

1. Calling api.registerTool() requires a contracts.tools manifest entry
   that we don't have. The registerTool call also caused the gateway to
   gate api.runtime.system, which is why the runner=runtime.system...
   line then crashed. Dropping the in-agent tool lets the plugin
   register cleanly as 'hook-only' (a supported compatibility path).
   The same publish action remains available via the 'learn' skill.

2. Even with the tool removed, runtime.system is only injected for
   trusted plugins. Switching the shell runner to Node's built-in
   child_process.spawn removes the dependency entirely — no trust gate
   to worry about.

Result: 'openclaw plugins doctor' now reports only the benign
'is hook-only / supported compatibility path' info, gateway boots
without errors, and hook events fire through to scripts/hook_entry.sh
without touching runtime.system.

Vitest mocks were rewritten to fake node:child_process.spawn; all 5
tests pass.
Two gating issues prevented the gateway from dispatching events to our
plugin even after it 'registered' cleanly:

1. Without manifest 'activation.onStartup: true', the gateway lazy-loads
   the plugin only when one of its activation triggers fires (providers,
   channels, routes). A hook-only plugin has none, so it's never loaded
   for actual session dispatch — even though plugins inspect reports
   'Status: loaded' (that's config-state, not gateway-state).

2. Non-bundled plugins must explicitly set
   plugins.entries.<id>.hooks.allowConversationAccess=true before the
   gateway will dispatch typed hooks to them. Otherwise every hook
   invocation is silently dropped with:
     [plugins] typed hook "agent_end" blocked because non-bundled plugins
     must set plugins.entries.<id>.hooks.allowConversationAccess=true

Fix:
* manifest: add activation.onStartup=true so the gateway loads us during
  startup and includes us in the dispatch fan-out.
* reflexio setup openclaw: set the allowConversationAccess flag during
  install so fresh installs don't hit the same silent-drop trap.

Verified locally: after both changes, gateway boot now lists
'11 plugins: ... reflexio-openclaw-smart, ...' (was 10), the
'typed hook blocked' log line is gone, and plugins doctor reports only
the benign 'is hook-only / supported compatibility path' info.
Two final fixes that unblocked the end-to-end Telegram pipeline:

1. state.py session-id charset now allows colons.
   openClaw sessionKeys for channel-routed agents are shaped
   agent:<id>:<run> (e.g. agent:main:telegram:default:direct:<peer>).
   My recent path-traversal regex rejected colons, so state.append()
   silently no-op'd and the sessions dir stayed empty even with hooks
   firing correctly. Colons are not POSIX path separators — they're safe
   inside filenames — and the explicit '..' / pure-dot guards still
   block real traversal attempts. Added colon-containing session ids to
   the regression tests.

2. index.ts subscribes to message_received and normalizes its
   event.content to event.prompt so the Python before_prompt_build
   handler receives a uniform payload shape across channel-side and
   agent-side hook invocations. Per the SDK type definitions the agent
   side delivers event.prompt while channel side delivers
   event.content — the normalization keeps the Python side handler
   shape stable.

Verified end-to-end via Telegram:
- Bot DM produced ~/.openclaw-smart/sessions/agent:main:telegram:default:direct:<peer>.jsonl
- Buffer contains User → Assistant_tool → Assistant turns
- agent_end fired and published to the reflexio backend on port 8071
- {"published_up_to": 4} watermark stamped, confirming publish succeeded

Also pruned the diagnostic 'subscribe-all' instrumentation back to the
7 hooks we actually rely on (session_start, message_received,
before_prompt_build, before_tool_call, after_tool_call, agent_end,
session_end). Diagnostic log.info hook-fire spam moved to log.debug.

Plugin tests: 140 passed. TS shim tests: 5 passed.
@yilu331 yilu331 force-pushed the feat/openclaw-smart-plugin branch from 9fbe317 to 0aaff89 Compare May 21, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant