fix(tools,api): address three additional review findings

- api_v3.py install_plugin_requirements: replace hardcoded plugin-repos
  fallback with config-driven resolution (plugin_system.plugins_directory),
  matching the pattern used elsewhere in the module
- api_v3.py _fix_json_arrays: recurse into converted and existing array
  elements when items.type is object, so nested numeric-keyed dicts inside
  array items are also normalized
- tools.html toolsAction: check r.ok before r.json() and recover
  gracefully from non-JSON error bodies (HTML 500 pages), consistent
  with the existing loadGitInfo guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-06-29 13:47:44 -04:00
parent 7e44ad3632
commit 04f96ec413
2 changed files with 42 additions and 22 deletions

View File

@@ -1682,7 +1682,13 @@ def execute_system_action():
}) })
elif action == 'install_plugin_requirements': elif action == 'install_plugin_requirements':
active_pm = getattr(api_v3, 'plugin_manager', None) active_pm = getattr(api_v3, 'plugin_manager', None)
plugins_dir = Path(active_pm.plugins_dir) if active_pm else PROJECT_ROOT / 'plugin-repos' if active_pm:
plugins_dir = Path(active_pm.plugins_dir)
else:
_cm = getattr(api_v3, 'config_manager', None)
_cfg = _cm.load_config() if _cm else {}
_dir_name = _cfg.get('plugin_system', {}).get('plugins_directory', 'plugin-repos')
plugins_dir = Path(_dir_name) if os.path.isabs(_dir_name) else PROJECT_ROOT / _dir_name
results = [] results = []
if plugins_dir.exists(): if plugins_dir.exists():
for p in sorted(plugins_dir.iterdir()): for p in sorted(plugins_dir.iterdir()):
@@ -4630,27 +4636,34 @@ def save_plugin_config():
continue continue
pt = ps.get('type') pt = ps.get('type')
val = cfg[k] val = cfg[k]
if pt == 'array' and isinstance(val, dict): if pt == 'array':
keys = list(val.keys()) items_schema = ps.get('items', {})
if keys and all(str(x).isdigit() for x in keys): item_type = items_schema.get('type')
sorted_keys = sorted(keys, key=lambda x: int(str(x))) if isinstance(val, dict):
items_schema = ps.get('items', {}) keys = list(val.keys())
item_type = items_schema.get('type') if keys and all(str(x).isdigit() for x in keys):
arr = [val[sk] for sk in sorted_keys] sorted_keys = sorted(keys, key=lambda x: int(str(x)))
if item_type in ('integer', 'number'): arr = [val[sk] for sk in sorted_keys]
converted = [] if item_type in ('integer', 'number'):
for v in arr: converted = []
if isinstance(v, str): for v in arr:
try: if isinstance(v, str):
converted.append(int(v) if item_type == 'integer' else float(v)) try:
except (ValueError, TypeError): converted.append(int(v) if item_type == 'integer' else float(v))
except (ValueError, TypeError):
converted.append(v)
else:
converted.append(v) converted.append(v)
else: arr = converted
converted.append(v) cfg[k] = arr
arr = converted elif not keys:
cfg[k] = arr cfg[k] = []
elif not keys: # Recurse into each element when items are objects with properties,
cfg[k] = [] # covering both freshly-converted and already-list values.
if item_type == 'object' and 'properties' in items_schema:
for elem in (cfg[k] if isinstance(cfg[k], list) else []):
if isinstance(elem, dict):
_fix_json_arrays(elem, items_schema['properties'])
elif pt == 'object' and 'properties' in ps and isinstance(val, dict): elif pt == 'object' and 'properties' in ps and isinstance(val, dict):
_fix_json_arrays(val, ps['properties']) _fix_json_arrays(val, ps['properties'])
_fix_json_arrays(plugin_config, schema['properties']) _fix_json_arrays(plugin_config, schema['properties'])

View File

@@ -229,7 +229,14 @@
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({action}) body: JSON.stringify({action})
}) })
.then(r => r.json()) .then(r => {
if (!r.ok) {
return r.json()
.then(d => Promise.reject(new Error(d.message || `HTTP ${r.status}`)))
.catch(() => Promise.reject(new Error(`HTTP ${r.status}`)));
}
return r.json();
})
.then(data => { .then(data => {
const ok = data.status === 'success'; const ok = data.status === 'success';
showResult( showResult(