feat(tool_policy): diagnostics RPC and Developer Options panel#2715
feat(tool_policy): diagnostics RPC and Developer Options panel#2715MrMrVlad wants to merge 5 commits into
Conversation
Expand tool_registry.diagnostics with policy posture, MCP allowlist summary, MCP write audit health, and a best-effort recent-denials buffer. Add a minimal Developer Options UI panel and unit coverage. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a recent-denial registry and records denials on blocked tool calls; extends diagnostics types and ops to be config-driven (posture, MCP allowlists, write-audit health, recent denials); updates RPC handler/tests; adds a settings panel, i18n strings, menu entry, and a route to surface diagnostics. ChangesTool-Policy Diagnostics Feature
Sequence Diagram(s): sequenceDiagram
participant Settings as Browser Settings Panel
participant RPC as handle_diagnostics (server)
participant Ops as tool_registry::ops::diagnostics
participant Denials as denials::list
participant DB as chunk_store (mcp_writes)
Settings->>RPC: GET diagnostics
RPC->>RPC: load config (timeout)
RPC->>Ops: diagnostics(&config)
Ops->>Denials: list(MAX_RECENT)
Ops->>DB: SELECT COUNT(*) FROM mcp_writes (last 24h)
Ops-->>RPC: ToolPolicyDiagnostics
RPC-->>Settings: JSON diagnostics
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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
`@app/src/components/settings/panels/__tests__/ToolPolicyDiagnosticsPanel.test.tsx`:
- Line 46: The failing assertion in ToolPolicyDiagnosticsPanel.test.tsx uses an
over-escaped regex for the literal parentheses; update the expect that calls
screen.getByText(...) so the regex escapes the parentheses only once (match the
literal "(24h)" rather than backslash characters), i.e. change the pattern used
in the getByText assertion to escape parentheses correctly for "Recent (24h):".
In `@app/src/components/settings/panels/ToolPolicyDiagnosticsPanel.tsx`:
- Around line 78-224: ToolPolicyDiagnosticsPanel renders many hard-coded user
strings (e.g., "Loading…", "Diagnostics unavailable", section headers like
"Policy posture", labels like "Autonomy:", empty states) that must be localized;
import and use useT() in the ToolPolicyDiagnosticsPanel component and replace
every literal JSX string (status messages, headings, dt/dd labels, empty-state
text, list placeholders like "<unnamed>") with t('...') calls using descriptive
keys (e.g., toolPolicy.loading, toolPolicy.unavailable,
toolPolicy.policyPosture.autonomy, toolPolicy.inventory.totalTools,
toolPolicy.mcpAllowlists.enabledLabel, toolPolicy.emptyUnnamed, etc.), then add
those same keys and English values to app/src/lib/i18n/en.ts in this PR so
translations load correctly.
In `@src/openhuman/agent/harness/session/turn.rs`:
- Around line 1331-1336: The call to
crate::openhuman::tool_registry::denials::record currently forwards the raw
reason into durable telemetry (using call.name, self.tool_policy.name(),
blocked_action, reason); replace this by redacting sensitive content before the
boundary: obtain a sanitized_reason via an existing redaction helper (or add one
e.g., redact_reason(reason) that strips PII/secrets or returns a redaction
token) and pass sanitized_reason to denials::record instead of reason, ensuring
only the pre-redacted string is persisted.
In `@src/openhuman/tool_registry/denials.rs`:
- Around line 20-21: The trimmed+truncated denial text in variable `reason` (set
via `truncate_reason(reason.trim())`) is persisted and later surfaced, so you
must redact sensitive data before storing or including it in diagnostics:
replace or wrap the stored value with a redaction step (e.g., call a
`redact_reason`/`sanitize_reason` function or inline redaction logic)
immediately after truncation and before any persistence or diagnostic creation;
apply the same change to the other occurrences referenced around lines 56–65 so
no raw/unredacted `reason` is ever written to records or logs.
- Around line 71-88: Tests are flaky because they share a global denial buffer;
make them deterministic by resetting the registry at test start: add a
clear/reset helper (e.g., reset_denials() or clear_denials()) in the denials
module and call it at the beginning of record_truncates_and_bounds and
record_ignores_empty_tool so each test starts with an empty global state; keep
using record, list and MAX_DENIALS as-is but ensure tests invoke the new reset
helper before exercising record/list so no cross-test races occur.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 91e32884-03d9-4135-9820-b7a9907aa647
📒 Files selected for processing (10)
app/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/ToolPolicyDiagnosticsPanel.tsxapp/src/components/settings/panels/__tests__/ToolPolicyDiagnosticsPanel.test.tsxapp/src/pages/Settings.tsxsrc/openhuman/agent/harness/session/turn.rssrc/openhuman/tool_registry/denials.rssrc/openhuman/tool_registry/mod.rssrc/openhuman/tool_registry/ops.rssrc/openhuman/tool_registry/schemas.rssrc/openhuman/tool_registry/types.rs
| crate::openhuman::tool_registry::denials::record( | ||
| call.name.as_str(), | ||
| self.tool_policy.name(), | ||
| blocked_action, | ||
| reason, | ||
| ); |
There was a problem hiding this comment.
Avoid persisting raw policy reasons into the denial registry.
This call forwards reason directly into a retained diagnostics buffer. Add redaction at this boundary (or pass a pre-redacted reason) so policy-block telemetry cannot retain sensitive text.
As per coding guidelines, “Never log secrets or full PII; always redact sensitive data in debug logs”.
🤖 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 `@src/openhuman/agent/harness/session/turn.rs` around lines 1331 - 1336, The
call to crate::openhuman::tool_registry::denials::record currently forwards the
raw reason into durable telemetry (using call.name, self.tool_policy.name(),
blocked_action, reason); replace this by redacting sensitive content before the
boundary: obtain a sanitized_reason via an existing redaction helper (or add one
e.g., redact_reason(reason) that strips PII/secrets or returns a redaction
token) and pass sanitized_reason to denials::record instead of reason, ensuring
only the pre-redacted string is persisted.
Localize diagnostics panel strings, redact sensitive denial reasons before persistence, and fix the Vitest assertion for Recent (24h) copy. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed CodeRabbit + CI feedback in 4e7a457 / follow-up:
Re-running CI — prior Rust core failure ( |
Co-authored-by: Cursor <cursoragent@cursor.com>
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 `@app/src/lib/i18n/en.ts`:
- Around line 516-543: The new i18n keys under devOptions.toolPolicyDiagnostics
(e.g., devOptions.toolPolicyDiagnostics.posture.autonomy, .inventory.totalTools,
.mcpAllowlists.summary, etc.) were added to app/src/lib/i18n/en.ts but not
propagated to the chunked locale files; update the matching en chunk file
(en-*.ts) and every non-English locale chunk (ar, bn, de, es, fr, hi, id, it,
ko, pt, ru, zh-CN) by adding the exact same key names and using the English
strings as placeholders so the UI won’t show missing text for chunked locales —
search for the devOptions.toolPolicyDiagnostics prefix to locate where to insert
the new keys in each chunk file.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d65c2d23-973f-4aaa-a5a0-9e4568113038
📒 Files selected for processing (6)
app/src/components/settings/panels/DeveloperOptionsPanel.tsxapp/src/components/settings/panels/ToolPolicyDiagnosticsPanel.tsxapp/src/components/settings/panels/__tests__/ToolPolicyDiagnosticsPanel.test.tsxapp/src/lib/i18n/en.tssrc/openhuman/tool_registry/denials.rssrc/openhuman/tool_registry/ops.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- app/src/components/settings/panels/tests/ToolPolicyDiagnosticsPanel.test.tsx
- app/src/components/settings/panels/DeveloperOptionsPanel.tsx
- app/src/components/settings/panels/ToolPolicyDiagnosticsPanel.tsx
- src/openhuman/tool_registry/ops.rs
| 'devOptions.toolPolicyDiagnosticsDesc': | ||
| 'Tool inventory, policy posture, MCP allowlists, and recent blocks', | ||
| 'devOptions.toolPolicyDiagnostics.loading': 'Loading…', | ||
| 'devOptions.toolPolicyDiagnostics.unavailable': 'Diagnostics unavailable', | ||
| 'devOptions.toolPolicyDiagnostics.posture.title': 'Policy posture', | ||
| 'devOptions.toolPolicyDiagnostics.posture.autonomy': 'Autonomy:', | ||
| 'devOptions.toolPolicyDiagnostics.posture.workspaceOnly': 'Workspace only:', | ||
| 'devOptions.toolPolicyDiagnostics.posture.maxActionsPerHour': 'Max actions/hr:', | ||
| 'devOptions.toolPolicyDiagnostics.posture.approvalMediumRisk': 'Approval (medium risk):', | ||
| 'devOptions.toolPolicyDiagnostics.posture.blockHighRisk': 'Block high risk:', | ||
| 'devOptions.toolPolicyDiagnostics.inventory.title': 'Inventory', | ||
| 'devOptions.toolPolicyDiagnostics.inventory.totalTools': 'Total tools', | ||
| 'devOptions.toolPolicyDiagnostics.inventory.enabledTools': 'Enabled tools', | ||
| 'devOptions.toolPolicyDiagnostics.inventory.mcpStdioTools': 'MCP stdio tools', | ||
| 'devOptions.toolPolicyDiagnostics.inventory.jsonRpcTools': 'JSON-RPC tools', | ||
| 'devOptions.toolPolicyDiagnostics.mcpAllowlists.title': 'MCP allowlists', | ||
| 'devOptions.toolPolicyDiagnostics.mcpAllowlists.summary': | ||
| 'Enabled: {enabled} · Servers: {enabledCount}/{totalCount}', | ||
| 'devOptions.toolPolicyDiagnostics.mcpAllowlists.unnamed': '<unnamed>', | ||
| 'devOptions.toolPolicyDiagnostics.mcpAllowlists.allowDeny': 'allow={allowCount} deny={denyCount}', | ||
| 'devOptions.toolPolicyDiagnostics.mcpWriteAudit.title': 'MCP write audit', | ||
| 'devOptions.toolPolicyDiagnostics.mcpWriteAudit.summary': | ||
| 'Enabled: {enabled} · Recent (24h): {recentRows}', | ||
| 'devOptions.toolPolicyDiagnostics.recentBlocked.title': 'Recent blocked calls', | ||
| 'devOptions.toolPolicyDiagnostics.recentBlocked.empty': 'No blocked calls recorded.', | ||
| 'devOptions.toolPolicyDiagnostics.redactedSurfaces.title': 'Redacted surfaces', | ||
| 'devOptions.toolPolicyDiagnostics.redactedSurfaces.summary': | ||
| 'Write-capable: {writeCount} · Policy surfaces: {policyCount}', |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking i18n parity for devOptions.toolPolicyDiagnostics.* keys"
# 1) Collect new key set from en.ts
EN_FILE="app/src/lib/i18n/en.ts"
if [[ ! -f "$EN_FILE" ]]; then
echo "Missing $EN_FILE"
exit 1
fi
keys=$(rg -n "'devOptions\.toolPolicyDiagnostics[^']*'" "$EN_FILE" \
| sed -E "s/.*'([^']+)'.*/\1/" \
| sort -u)
if [[ -z "${keys}" ]]; then
echo "No toolPolicyDiagnostics keys found in en.ts"
exit 0
fi
echo "Found keys:"
echo "$keys"
echo
# 2) Find locale/chunk files
mapfile -t locale_files < <(fd -p 'app/src/lib/i18n' -t f '^(en|ar|bn|de|es|fr|hi|id|it|ko|pt|ru|zh-CN)(-[0-9]+)?\.ts$' | sort)
if [[ ${`#locale_files`[@]} -eq 0 ]]; then
echo "No locale files found under app/src/lib/i18n"
exit 1
fi
missing_any=0
for f in "${locale_files[@]}"; do
missing_keys=()
while IFS= read -r k; do
if ! rg -n "'$k'" "$f" >/dev/null; then
missing_keys+=("$k")
fi
done <<< "$keys"
if [[ ${`#missing_keys`[@]} -gt 0 ]]; then
missing_any=1
echo "MISSING in $f:"
printf ' - %s\n' "${missing_keys[@]}"
echo
fi
done
if [[ $missing_any -eq 0 ]]; then
echo "✅ All locale/chunk files contain the new keys."
else
echo "❌ Some locale/chunk files are missing keys."
exit 2
fiRepository: tinyhumansai/openhuman
Length of output: 766
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking i18n parity for devOptions.toolPolicyDiagnostics.* keys"
EN_FILE="app/src/lib/i18n/en.ts"
if [[ ! -f "$EN_FILE" ]]; then
echo "Missing $EN_FILE"
exit 1
fi
# Collect all devOptions.toolPolicyDiagnostics.* keys from en.ts
keys_file="$(mktemp)"
rg -n "'devOptions\.toolPolicyDiagnostics[^']*'" "$EN_FILE" \
| sed -E "s/.*'([^']+)'.*/\1/" \
| sort -u > "$keys_file"
if [[ ! -s "$keys_file" ]]; then
echo "No toolPolicyDiagnostics keys found in en.ts"
exit 0
fi
echo "Key count: $(wc -l < "$keys_file")"
# Find locale/chunk files without process substitution
# (process substitution previously failed with /dev/fd issues in this environment)
locale_list_file="$(mktemp)"
fd -p 'app/src/lib/i18n' -t f '^(en|ar|bn|de|es|fr|hi|id|it|ko|pt|ru|zh-CN)(-[0-9]+)?\.ts$' \
| sort > "$locale_list_file"
mapfile -t locale_files < "$locale_list_file"
if [[ ${`#locale_files`[@]} -eq 0 ]]; then
echo "No locale files found under app/src/lib/i18n"
exit 1
fi
missing_any=0
missing_report_file="$(mktemp)"
while IFS= read -r f; do
missing_keys_file="$(mktemp)"
while IFS= read -r k; do
if ! rg -n "'$k'" "$f" >/dev/null; then
echo "$k" >> "$missing_keys_file"
fi
done < "$keys_file"
if [[ -s "$missing_keys_file" ]]; then
missing_any=1
{
echo "MISSING in $f:"
sed 's/^/ - /' "$missing_keys_file"
echo
} >> "$missing_report_file"
fi
rm -f "$missing_keys_file"
done < "$locale_list_file"
if [[ $missing_any -eq 0 ]]; then
echo "✅ All locale/chunk files contain the new keys."
else
echo "❌ Some locale/chunk files are missing keys:"
cat "$missing_report_file"
exit 2
fiRepository: tinyhumansai/openhuman
Length of output: 310
🏁 Script executed:
#!/bin/bash
set -euo pipefail
EN_FILE="app/src/lib/i18n/en.ts"
if [[ ! -f "$EN_FILE" ]]; then
echo "Missing $EN_FILE" >&2
exit 1
fi
# Collect new keys from en.ts
keys_file="$(mktemp)"
rg -n "'devOptions\.toolPolicyDiagnostics[^']*'" "$EN_FILE" \
| sed -E "s/.*'([^']+)'.*/\1/" \
| sort -u > "$keys_file"
echo "Key count: $(wc -l < "$keys_file")"
# Enumerate locale/chunk files using find (no fd path flag ambiguity)
# Targets:
# - en.ts
# - en-<N>.ts
# - ar.ts, ar-<N>.ts, bn.ts, bn-<N>.ts, ..., zh-CN.ts, zh-CN-<N>.ts
locale_files_file="$(mktemp)"
find app/src/lib/i18n -type f \( \
-name 'en.ts' -o -name 'en-*.ts' \
-o -name 'ar.ts' -o -name 'ar-*.ts' \
-o -name 'bn.ts' -o -name 'bn-*.ts' \
-o -name 'de.ts' -o -name 'de-*.ts' \
-o -name 'es.ts' -o -name 'es-*.ts' \
-o -name 'fr.ts' -o -name 'fr-*.ts' \
-o -name 'hi.ts' -o -name 'hi-*.ts' \
-o -name 'id.ts' -o -name 'id-*.ts' \
-o -name 'it.ts' -o -name 'it-*.ts' \
-o -name 'ko.ts' -o -name 'ko-*.ts' \
-o -name 'pt.ts' -o -name 'pt-*.ts' \
-o -name 'ru.ts' -o -name 'ru-*.ts' \
-o -name 'zh-CN.ts' -o -name 'zh-CN-*.ts' \
\) | sort > "$locale_files_file"
mapfile -t locale_files < "$locale_files_file"
echo "Locale files found: ${`#locale_files`[@]}"
missing_any=0
missing_report_file="$(mktemp)"
for f in "${locale_files[@]}"; do
missing_keys_file="$(mktemp)"
while IFS= read -r k; do
if ! rg -n "'$k'" "$f" >/dev/null; then
echo "$k" >> "$missing_keys_file"
fi
done < "$keys_file"
if [[ -s "$missing_keys_file" ]]; then
missing_any=1
{
echo "MISSING in $f:"
sed 's/^/ - /' "$missing_keys_file"
echo
} >> "$missing_report_file"
fi
rm -f "$missing_keys_file"
done
if [[ $missing_any -eq 0 ]]; then
echo "✅ All locale/chunk files contain the new keys."
else
echo "❌ Some locale/chunk files are missing keys:"
cat "$missing_report_file"
exit 2
fiRepository: tinyhumansai/openhuman
Length of output: 60380
🏁 Script executed:
#!/bin/bash
set -euo pipefail
EN_FILE="app/src/lib/i18n/en.ts"
keys_file="$(mktemp)"
# Extract ONLY i18n key names (the quoted object keys) that end with a colon.
# Example lines supported:
# '...': 'Some value'
# '...':
# 'Some value',
rg -n "'devOptions\.toolPolicyDiagnostics[^']*'\s*:" "$EN_FILE" \
| sed -E "s/.*'([^']+)'.*/\1/" \
| sort -u > "$keys_file"
echo "devOptions.toolPolicyDiagnostics key count: $(wc -l < "$keys_file")"
# Enumerate locale/chunk files under app/src/lib/i18n
locale_files_file="$(mktemp)"
find app/src/lib/i18n -type f \( \
-name 'en.ts' -o -name 'en-*.ts' \
-o -name 'ar.ts' -o -name 'ar-*.ts' \
-o -name 'bn.ts' -o -name 'bn-*.ts' \
-o -name 'de.ts' -o -name 'de-*.ts' \
-o -name 'es.ts' -o -name 'es-*.ts' \
-o -name 'fr.ts' -o -name 'fr-*.ts' \
-o -name 'hi.ts' -o -name 'hi-*.ts' \
-o -name 'id.ts' -o -name 'id-*.ts' \
-o -name 'it.ts' -o -name 'it-*.ts' \
-o -name 'ko.ts' -o -name 'ko-*.ts' \
-o -name 'pt.ts' -o -name 'pt-*.ts' \
-o -name 'ru.ts' -o -name 'ru-*.ts' \
-o -name 'zh-CN.ts' -o -name 'zh-CN-*.ts' \
\) | sort > "$locale_files_file"
mapfile -t locale_files < "$locale_files_file"
echo "Locale/chunk files found: ${`#locale_files`[@]}"
missing_report_file="$(mktemp)"
missing_any=0
# Compare by presence of the exact key token "'<key>'"
while IFS= read -r f; do
missing_keys_file="$(mktemp)"
while IFS= read -r k; do
token="'$k'"
if ! rg -nF "$token" "$f" >/dev/null; then
echo "$k" >> "$missing_keys_file"
fi
done < "$keys_file"
if [[ -s "$missing_keys_file" ]]; then
missing_any=1
{
echo "MISSING in $f:"
# keep output bounded
head -n 40 "$missing_keys_file"
if [[ $(wc -l < "$missing_keys_file") -gt 40 ]]; then
echo " ... (and $(( $(wc -l < "$missing_keys_file") - 40 )) more)"
fi
echo
} >> "$missing_report_file"
fi
rm -f "$missing_keys_file"
done < "$locale_files_file"
if [[ $missing_any -eq 0 ]]; then
echo "✅ All locale/chunk files contain every new devOptions.toolPolicyDiagnostics key."
else
echo "❌ Missing keys detected (showing up to 40 per file):"
cat "$missing_report_file"
exit 2
fiRepository: tinyhumansai/openhuman
Length of output: 45495
🏁 Script executed:
#!/bin/bash
set -euo pipefail
EN_FILE="app/src/lib/i18n/en.ts"
CHUNKS_DIR="app/src/lib/i18n/chunks"
locales=(ar bn de es fr hi id it ko pt ru zh-CN)
# Extract all i18n keys under devOptions.toolPolicyDiagnostics... from en.ts
keys_file="$(mktemp)"
rg -o "'devOptions\.toolPolicyDiagnostics[^']*'" "$EN_FILE" \
| sed "s/^'//; s/'$//" \
| sort -u > "$keys_file"
echo "devOptions.toolPolicyDiagnostics key count: $(wc -l < "$keys_file")"
# English chunk files
mapfile -t en_chunk_files < <(find "$CHUNKS_DIR" -type f -name 'en-*.ts' | sort)
missing_total=0
missing_report_file="$(mktemp)"
for key in $(cat "$keys_file"); do
# Find which en-N.ts contains this key
matches=()
for f in "${en_chunk_files[@]}"; do
if rg -nF "'$key'" "$f" >/dev/null; then
matches+=("$f")
fi
done
if [[ ${`#matches`[@]} -ne 1 ]]; then
echo "KEY '$key' appears in ${`#matches`[@]} English chunk files: ${matches[*]:-<none>}" >> "$missing_report_file"
missing_total=1
continue
fi
en_chunk="${matches[0]}"
base="$(basename "$en_chunk")" # en-<N>.ts
N="$(sed -E 's/^en-([0-9]+)\.ts$/\1/' <<< "$base")"
for loc in "${locales[@]}"; do
loc_chunk="$CHUNKS_DIR/${loc}-${N}.ts"
if [[ ! -f "$loc_chunk" ]]; then
echo "MISSING file: $loc_chunk (for key '$key')" >> "$missing_report_file"
missing_total=1
continue
fi
if ! rg -nF "'$key'" "$loc_chunk" >/dev/null; then
echo "MISSING key '$key' in $loc_chunk" >> "$missing_report_file"
missing_total=1
fi
done
done
if [[ $missing_total -eq 0 ]]; then
echo "✅ All devOptions.toolPolicyDiagnostics keys are present in the corresponding en-N.ts and in every locale's ${loc}-N.ts chunk."
else
echo "❌ Missing i18n chunk parity:"
# limit output
head -n 120 "$missing_report_file"
echo "... (see full report in $missing_report_file)"
exit 2
fiRepository: tinyhumansai/openhuman
Length of output: 189
🏁 Script executed:
#!/bin/bash
set -euo pipefail
EN_FILE="app/src/lib/i18n/en.ts"
CHUNKS_DIR="app/src/lib/i18n/chunks"
locales=(ar bn de es fr hi id it ko pt ru zh-CN)
# Extract devOptions.toolPolicyDiagnostics keys from en.ts:
# Capture the quoted key BEFORE the colon (not the value).
keys_file="$(mktemp)"
rg -n "'devOptions\.toolPolicyDiagnostics[^']*'\s*:" "$EN_FILE" \
| sed -E "s/^.*'([^']+)'\s*:\s*.*$/\1/" \
| sort -u > "$keys_file"
echo "devOptions.toolPolicyDiagnostics key count: $(wc -l < "$keys_file")"
# List English chunk files (en-<N>.ts)
en_chunk_files_file="$(mktemp)"
find "$CHUNKS_DIR" -type f -name 'en-*.ts' | sort > "$en_chunk_files_file"
mapfile -t en_chunk_files < "$en_chunk_files_file"
echo "English chunk files: ${`#en_chunk_files`[@]}"
missing_any=0
missing_report_file="$(mktemp)"
while IFS= read -r key; do
# Find the en-<N>.ts that contains this key
matches_file="$(mktemp)"
for f in "${en_chunk_files[@]}"; do
if rg -nF "'$key'" "$f" >/dev/null; then
echo "$f" >> "$matches_file"
fi
done
matches_count=$(wc -l < "$matches_file")
if [[ $matches_count -ne 1 ]]; then
missing_any=1
{
echo "KEY present in $matches_count English chunk files:"
sed 's/^/ - /' "$matches_file"
echo " key: $key"
echo
} >> "$missing_report_file"
rm -f "$matches_file"
continue
fi
en_chunk="$(cat "$matches_file")"
rm -f "$matches_file"
base="$(basename "$en_chunk")" # en-<N>.ts
N="$(sed -E 's/^en-([0-9]+)\.ts$/\1/' <<< "$base")"
for loc in "${locales[@]}"; do
loc_chunk="$CHUNKS_DIR/${loc}-${N}.ts"
if [[ ! -f "$loc_chunk" ]]; then
missing_any=1
echo "MISSING file: $loc_chunk (for key $key)" >> "$missing_report_file"
continue
fi
if ! rg -nF "'$key'" "$loc_chunk" >/dev/null; then
missing_any=1
echo "MISSING key '$key' in $loc_chunk" >> "$missing_report_file"
fi
done
done < "$keys_file"
if [[ $missing_any -eq 0 ]]; then
echo "✅ All devOptions.toolPolicyDiagnostics keys exist in corresponding en-<N>.ts and every ${locales[*]}-<N>.ts"
else
echo "❌ Missing i18n chunk parity:"
head -n 80 "$missing_report_file"
echo "... (see full report in $missing_report_file)"
exit 2
fiRepository: tinyhumansai/openhuman
Length of output: 2583
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "devOptions\.toolPolicyDiagnostics\.inventory\.title" app/src/lib/i18n/chunks/en-*.ts || true
rg -n "devOptions\.toolPolicyDiagnostics\.mcpAllowlists\.summary" app/src/lib/i18n/chunks/en-*.ts || true
rg -n "devOptions\.toolPolicyDiagnostics\.inventory\.title" app/src/lib/i18n/chunks/ar-*.ts || true
# Also check en.ts for sanity
rg -n "devOptions\.toolPolicyDiagnostics\.inventory\.title" app/src/lib/i18n/en.ts || trueRepository: tinyhumansai/openhuman
Length of output: 137
🏁 Script executed:
#!/bin/bash
set -euo pipefail
keys=(
"devOptions.toolPolicyDiagnostics.inventory.title"
"devOptions.toolPolicyDiagnostics.loading"
"devOptions.toolPolicyDiagnostics.mcpAllowlists.summary"
"devOptions.toolPolicyDiagnostics.redactedSurfaces.title"
)
for k in "${keys[@]}"; do
echo "---- $k in en chunks"
rg -n "$k" app/src/lib/i18n/chunks/en-*.ts || true
done
echo "---- sanity check: same keys in ar chunks"
for k in "${keys[@]}"; do
rg -n "$k" app/src/lib/i18n/chunks/ar-*.ts || true
doneRepository: tinyhumansai/openhuman
Length of output: 381
Propagate the new devOptions.toolPolicyDiagnostics.* i18n keys into all chunked locale files
The keys added in app/src/lib/i18n/en.ts (devOptions.toolPolicyDiagnostics.*) are missing from app/src/lib/i18n/chunks/en-*.ts and corresponding non-English chunk files (e.g., ar-*.ts), which will cause missing UI text for chunked locales. Add these exact keys to the matching en-N.ts chunk and to every non-English locale chunk (ar, bn, de, es, fr, hi, id, it, ko, pt, ru, zh-CN) using the English value as the placeholder if needed.
🤖 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 `@app/src/lib/i18n/en.ts` around lines 516 - 543, The new i18n keys under
devOptions.toolPolicyDiagnostics (e.g.,
devOptions.toolPolicyDiagnostics.posture.autonomy, .inventory.totalTools,
.mcpAllowlists.summary, etc.) were added to app/src/lib/i18n/en.ts but not
propagated to the chunked locale files; update the matching en chunk file
(en-*.ts) and every non-English locale chunk (ar, bn, de, es, fr, hi, id, it,
ko, pt, ru, zh-CN) by adding the exact same key names and using the English
strings as placeholders so the UI won’t show missing text for chunked locales —
search for the devOptions.toolPolicyDiagnostics prefix to locate where to insert
the new keys in each chunk file.
|
@MrMrVlad this PR has merge conflicts with main — please rebase/resolve before review. |
|
This PR has merge conflicts with main — please rebase/resolve before review. |
Summary
tool_registry.diagnosticswith policy posture, MCP allowlist summary, MCP write-audit health, and recent policy denials (redacted).Problem
When tools are hidden or policy blocks execution, operators today must reconstruct state from logs and config. Issue #2136 asks for a single diagnostics surface showing inventory, policy mode, MCP allowlists, audit health, and recent denials—without exposing secrets.
Solution
ToolPolicyDiagnosticsnow composes registry counts, autonomy posture, per-server MCP allow/deny list sizes, best-effortmcp_writesrow count (24h), and up to 25 recent denials fromtool_registry::denials.turn.rs, append a truncated, redacted denial record.ToolPolicyDiagnosticsPanelcallstool_registry.diagnosticsand sections the payload for quick review.This is the first slice of #2136 (observability + UI). Conformance reporting (runtime contract hash, hidden raw-write tool checks, copyable support bundle) can follow in a separate PR.
Submission Checklist
.github/workflows/coverage.yml; local diff-cover not run pre-submit## Related— N/A: no matrix feature IDs touchedCloses #NNNin the## RelatedsectionImpact
Related
Test plan
cargo test tool_registry --lib(diagnostics + denials ring buffer)pnpm test—ToolPolicyDiagnosticsPanel.test.tsxpnpm format:check,pnpm lint,pnpm compile,pnpm rust:check(pre-push hook)AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
feat/2136-tool-policy-diagnostics3c63b46c1b9c5e8a265b33376932a272d412355aValidation Run
pnpm --filter openhuman-app format:checkpnpm typecheck/pnpm compilecargo test tool_registry --lib, Vitest panel testpnpm rust:checkpnpm rust:checkValidation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
tool_registry.list/getunchanged.Duplicate / Superseded PR Handling
Summary by CodeRabbit
New Features
Tests
Documentation