feat(openclaw): replace TS integration with claude-smart-style openclaw-smart plugin#80
feat(openclaw): replace TS integration with claude-smart-style openclaw-smart plugin#80yilu331 wants to merge 53 commits into
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughReplaces 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 ChangesOpenClaw Smart Integration
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
Estimated code review effort: Possibly related PRs:
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 winFix 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 winGuard
reflexio_publishwhen 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 winAvoid 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 winClarify 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 winHarden
unpublished_sliceagainst malformed record shapes.Line 217 and Line 234 assume
published_up_tois numeric andtool_inputis 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 winReplace hardcoded
/tmpwithtmp_pathin 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 winLocalize 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 winAssert a single publish call in the fallback-path test.
Line 36 reads
call_argsdirectly; this won’t catch accidental multiple invocations. Addassert_called_once()and validate the same key flags you assert in thesessionKeytest.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
⛔ Files ignored due to path filters (2)
reflexio/integrations/openclaw/package-lock.jsonis excluded by!**/package-lock.jsonreflexio/integrations/openclaw/plugin/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (120)
reflexio/cli/README.mdreflexio/cli/app.pyreflexio/cli/commands/embeddings.pyreflexio/cli/commands/services.pyreflexio/cli/commands/setup_cmd.pyreflexio/cli/run_services.pyreflexio/cli/stop_services.pyreflexio/cli/utils.pyreflexio/integrations/claude_code/README.mdreflexio/integrations/claude_code/hook/HOOK.mdreflexio/integrations/claude_code/hook/handler.jsreflexio/integrations/claude_code/hook/search_hook.jsreflexio/integrations/claude_code/hook/session_start_hook.shreflexio/integrations/openclaw/README.mdreflexio/integrations/openclaw/TESTING.mdreflexio/integrations/openclaw/hook/handler.jsreflexio/integrations/openclaw/package.jsonreflexio/integrations/openclaw/plugin/.gitignorereflexio/integrations/openclaw/plugin/README.mdreflexio/integrations/openclaw/plugin/hook/handler.tsreflexio/integrations/openclaw/plugin/hook/setup.tsreflexio/integrations/openclaw/plugin/index.tsreflexio/integrations/openclaw/plugin/lib/publish.tsreflexio/integrations/openclaw/plugin/lib/search.tsreflexio/integrations/openclaw/plugin/lib/server.tsreflexio/integrations/openclaw/plugin/lib/sqlite-buffer.tsreflexio/integrations/openclaw/plugin/lib/user-id.tsreflexio/integrations/openclaw/plugin/openclaw.plugin.jsonreflexio/integrations/openclaw/plugin/package.jsonreflexio/integrations/openclaw/plugin/pyproject.tomlreflexio/integrations/openclaw/plugin/rules/reflexio.mdreflexio/integrations/openclaw/plugin/scripts/_lib.shreflexio/integrations/openclaw/plugin/scripts/backend-log-runner.shreflexio/integrations/openclaw/plugin/scripts/backend-service.shreflexio/integrations/openclaw/plugin/scripts/cli.shreflexio/integrations/openclaw/plugin/scripts/dashboard-open.shreflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.shreflexio/integrations/openclaw/plugin/scripts/hook_entry.shreflexio/integrations/openclaw/plugin/scripts/smart-install.shreflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.mdreflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.mdreflexio/integrations/openclaw/plugin/skills/learn/SKILL.mdreflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.mdreflexio/integrations/openclaw/plugin/skills/restart/SKILL.mdreflexio/integrations/openclaw/plugin/skills/show/SKILL.mdreflexio/integrations/openclaw/plugin/src/openclaw_smart/__init__.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/state.pyreflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.tsreflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.tsreflexio/integrations/openclaw/plugin/tests/__init__.pyreflexio/integrations/openclaw/plugin/tests/integration/__init__.pyreflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.pyreflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.pyreflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.pyreflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.pyreflexio/integrations/openclaw/plugin/tests/test_cli.pyreflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.pyreflexio/integrations/openclaw/plugin/tests/test_events_agent_end.pyreflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.pyreflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.pyreflexio/integrations/openclaw/plugin/tests/test_events_session_end.pyreflexio/integrations/openclaw/plugin/tests/test_events_session_start.pyreflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.pyreflexio/integrations/openclaw/plugin/tests/test_ids.pyreflexio/integrations/openclaw/plugin/tests/test_internal_call.pyreflexio/integrations/openclaw/plugin/tests/test_oc_cite.pyreflexio/integrations/openclaw/plugin/tests/test_query_compose.pyreflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.pyreflexio/integrations/openclaw/plugin/tests/test_runtime.pyreflexio/integrations/openclaw/plugin/tests/test_state.pyreflexio/integrations/openclaw/plugin/tsconfig.jsonreflexio/integrations/openclaw/plugin/types/openclaw.d.tsreflexio/integrations/openclaw/plugin/vitest.config.tsreflexio/integrations/openclaw/publish_clawhub.shreflexio/integrations/openclaw/references/HOOK.mdreflexio/integrations/openclaw/scripts/install.shreflexio/integrations/openclaw/scripts/uninstall.shreflexio/integrations/openclaw/tests/publish.test.tsreflexio/integrations/openclaw/tests/search.test.tsreflexio/integrations/openclaw/tests/server.test.tsreflexio/integrations/openclaw/tests/setup.test.tsreflexio/integrations/openclaw/tests/sqlite-buffer.test.tsreflexio/integrations/openclaw/tests/user-id.test.tsreflexio/integrations/openclaw/vitest.config.tsreflexio/server/llm/embedding_service.pyreflexio/server/llm/litellm_client.pyreflexio/server/llm/providers/embedding_service_provider.pyreflexio/server/llm/providers/local_embedding_provider.pyreflexio/server/llm/providers/nomic_embedding_provider.pyreflexio/server/llm/providers/openclaw_provider.pyreflexio/server/services/storage/sqlite_storage/_base.pyreflexio/server/services/storage/sqlite_storage/_profiles.pytests/cli/test_embeddings_cmd.pytests/cli/test_service_builders.pytests/cli/test_services_first_run.pytests/cli/test_setup_cmd.pytests/server/llm/test_embedding_service_provider.pytests/server/llm/test_openclaw_provider.pytests/server/llm/test_openclaw_provider_integration.pytests/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
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)
a99a8f3 to
408ec34
Compare
|
Force-pushed Addressed
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
|
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (2)
reflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.py (1)
526-549:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEnsure 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 winSurface install-failure context for
session-startafter inline bootstrap.The second failure-marker gate returns an empty payload for all events. When install fails during this same invocation,
session-startloses 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
⛔ Files ignored due to path filters (2)
reflexio/integrations/openclaw/package-lock.jsonis excluded by!**/package-lock.jsonreflexio/integrations/openclaw/plugin/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (98)
reflexio/cli/commands/setup_cmd.pyreflexio/integrations/openclaw/README.mdreflexio/integrations/openclaw/TESTING.mdreflexio/integrations/openclaw/hook/handler.jsreflexio/integrations/openclaw/package.jsonreflexio/integrations/openclaw/plugin/.gitignorereflexio/integrations/openclaw/plugin/README.mdreflexio/integrations/openclaw/plugin/hook/handler.tsreflexio/integrations/openclaw/plugin/hook/setup.tsreflexio/integrations/openclaw/plugin/index.tsreflexio/integrations/openclaw/plugin/lib/publish.tsreflexio/integrations/openclaw/plugin/lib/search.tsreflexio/integrations/openclaw/plugin/lib/server.tsreflexio/integrations/openclaw/plugin/lib/sqlite-buffer.tsreflexio/integrations/openclaw/plugin/lib/user-id.tsreflexio/integrations/openclaw/plugin/openclaw.plugin.jsonreflexio/integrations/openclaw/plugin/package.jsonreflexio/integrations/openclaw/plugin/pyproject.tomlreflexio/integrations/openclaw/plugin/rules/reflexio.mdreflexio/integrations/openclaw/plugin/scripts/_lib.shreflexio/integrations/openclaw/plugin/scripts/backend-log-runner.shreflexio/integrations/openclaw/plugin/scripts/backend-service.shreflexio/integrations/openclaw/plugin/scripts/cli.shreflexio/integrations/openclaw/plugin/scripts/dashboard-open.shreflexio/integrations/openclaw/plugin/scripts/ensure-plugin-root.shreflexio/integrations/openclaw/plugin/scripts/hook_entry.shreflexio/integrations/openclaw/plugin/scripts/smart-install.shreflexio/integrations/openclaw/plugin/skills/clear-all/SKILL.mdreflexio/integrations/openclaw/plugin/skills/dashboard/SKILL.mdreflexio/integrations/openclaw/plugin/skills/learn/SKILL.mdreflexio/integrations/openclaw/plugin/skills/reflexio/SKILL.mdreflexio/integrations/openclaw/plugin/skills/restart/SKILL.mdreflexio/integrations/openclaw/plugin/skills/show/SKILL.mdreflexio/integrations/openclaw/plugin/src/openclaw_smart/__init__.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/context_format.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/context_inject.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/__init__.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/after_tool_call.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/agent_end.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_prompt_build.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/before_tool_call.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_end.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/events/session_start.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/hook.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/ids.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/internal_call.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/oc_cite.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/optimizer_assistant.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/query_compose.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/reflexio_adapter.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/runtime.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/stall_banner.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/state.pyreflexio/integrations/openclaw/plugin/tests-ts/__mocks__/plugin-entry-stub.tsreflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.tsreflexio/integrations/openclaw/plugin/tests/__init__.pyreflexio/integrations/openclaw/plugin/tests/integration/__init__.pyreflexio/integrations/openclaw/plugin/tests/integration/test_e2e_session_loop.pyreflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.pyreflexio/integrations/openclaw/plugin/tests/integration/test_recursion_guard_integration.pyreflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.pyreflexio/integrations/openclaw/plugin/tests/test_cli.pyreflexio/integrations/openclaw/plugin/tests/test_events_after_tool_call.pyreflexio/integrations/openclaw/plugin/tests/test_events_agent_end.pyreflexio/integrations/openclaw/plugin/tests/test_events_before_prompt_build.pyreflexio/integrations/openclaw/plugin/tests/test_events_before_tool_call.pyreflexio/integrations/openclaw/plugin/tests/test_events_session_end.pyreflexio/integrations/openclaw/plugin/tests/test_events_session_start.pyreflexio/integrations/openclaw/plugin/tests/test_hook_dispatch.pyreflexio/integrations/openclaw/plugin/tests/test_ids.pyreflexio/integrations/openclaw/plugin/tests/test_internal_call.pyreflexio/integrations/openclaw/plugin/tests/test_oc_cite.pyreflexio/integrations/openclaw/plugin/tests/test_query_compose.pyreflexio/integrations/openclaw/plugin/tests/test_reflexio_adapter.pyreflexio/integrations/openclaw/plugin/tests/test_runtime.pyreflexio/integrations/openclaw/plugin/tests/test_state.pyreflexio/integrations/openclaw/plugin/tsconfig.build.jsonreflexio/integrations/openclaw/plugin/tsconfig.jsonreflexio/integrations/openclaw/plugin/types/openclaw.d.tsreflexio/integrations/openclaw/plugin/vitest.config.tsreflexio/integrations/openclaw/publish_clawhub.shreflexio/integrations/openclaw/references/HOOK.mdreflexio/integrations/openclaw/scripts/install.shreflexio/integrations/openclaw/scripts/uninstall.shreflexio/integrations/openclaw/tests/publish.test.tsreflexio/integrations/openclaw/tests/search.test.tsreflexio/integrations/openclaw/tests/server.test.tsreflexio/integrations/openclaw/tests/setup.test.tsreflexio/integrations/openclaw/tests/sqlite-buffer.test.tsreflexio/integrations/openclaw/tests/user-id.test.tsreflexio/integrations/openclaw/vitest.config.tsreflexio/server/llm/litellm_client.pyreflexio/server/llm/providers/openclaw_provider.pytests/cli/test_setup_cmd.pytests/server/llm/test_openclaw_provider.pytests/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
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
reflexio/cli/commands/setup_cmd.pyreflexio/integrations/openclaw/plugin/index.tsreflexio/integrations/openclaw/plugin/openclaw.plugin.jsonreflexio/integrations/openclaw/plugin/src/openclaw_smart/state.pyreflexio/integrations/openclaw/plugin/tests-ts/test_shim_dispatch.test.tsreflexio/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
There was a problem hiding this comment.
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
📒 Files selected for processing (12)
reflexio/cli/commands/setup_cmd.pyreflexio/integrations/openclaw/plugin/scripts/backend-service.shreflexio/integrations/openclaw/plugin/src/openclaw_smart/cli.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/publish.pyreflexio/integrations/openclaw/plugin/src/openclaw_smart/state.pyreflexio/integrations/openclaw/plugin/tests/integration/test_publish_to_local_reflexio_integration.pyreflexio/integrations/openclaw/plugin/tests/integration/test_search_inject_integration.pyreflexio/integrations/openclaw/plugin/tests/test_cli.pyreflexio/integrations/openclaw/plugin/tests/test_events_session_end.pyreflexio/integrations/openclaw/plugin/tests/test_publish.pyreflexio/integrations/openclaw/plugin/tests/test_state.pytests/cli/test_setup_cmd.py
| 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 winBackend 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 tohttp://localhost:8081and 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
📒 Files selected for processing (7)
reflexio/integrations/openclaw/README.mdreflexio/integrations/openclaw/npm/openclaw-smart/bin/openclaw-smart.jsreflexio/integrations/openclaw/npm/openclaw-smart/package.jsonreflexio/integrations/openclaw/plugin/README.mdreflexio/integrations/openclaw/plugin/package.jsonreflexio/integrations/openclaw/plugin/scripts/npm-cli.jsreflexio/integrations/openclaw/plugin/tests-ts/test_npm_cli.test.ts
| `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. |
There was a problem hiding this comment.
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.
| `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.
| function resolveOpenClawBin() { | ||
| const configured = process.env.OPENCLAW_BIN; | ||
| if (configured && existsSync(configured)) return configured; | ||
| return findExecutable("openclaw"); | ||
| } |
There was a problem hiding this comment.
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.
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).
…=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.
…ashboard, restart, clear-all)
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.
9fbe317 to
0aaff89
Compare
Summary
Replaces the prior TS-only
reflexio/integrations/openclaw/plugin/with a thin TS shim + Pythonopenclaw_smart/package that mirrorsclaude-smart1:1.reflexio/server/llm/providers/openclaw_provider.py— LiteLLMCustomLLMthat uses openClaw's CLI for extraction (analog ofclaude_code_provider.py).reflexio setup openclawfor the new plugin (plugin idreflexio-openclaw-smart, env writes forOPENCLAW_BIN+OPENCLAW_SMART_USE_LOCAL_CLI,--repair/--purgeflags).[oc:…]), secret masking, watermark-based publish resume, recursion guard.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.mddocs/superpowers/plans/2026-05-19-openclaw-smart.mdTest plan
cd reflexio/integrations/openclaw/plugin && uv run pytest tests/ -o 'addopts='→ 136 passed, 2 skipped (live-backend tests)npx tsc --noEmit && npx vitest run→ 5 passedreflexio/server/llm/providers/openclaw_provider: 23 unit + 3 integration tests passreflexio setup openclawtests: 6 unit tests pass (plugin id constant, env writes, install argv)reflexio setup openclaw→ real openClaw session → observe publish + inject)Companion parent-repo PR with docs + submodule bump will follow.
Summary by CodeRabbit
New Features
Improvements
Tests