mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-12 21:28:32 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d50a87493 |
+50
-2
@@ -934,6 +934,16 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
|
|||||||
if not req_file.exists():
|
if not req_file.exists():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# Packaged/distributed hosts (e.g. slopsmith-desktop) set
|
||||||
|
# SLOPSMITH_SKIP_PLUGIN_INSTALL to skip the blocking startup pip install:
|
||||||
|
# heavy optional deps (torch/whisperx/demucs) would otherwise download for
|
||||||
|
# minutes and hang the backend past the host app's readiness window. The
|
||||||
|
# plugin still loads and degrades gracefully when its optional deps are
|
||||||
|
# absent.
|
||||||
|
if os.environ.get("SLOPSMITH_SKIP_PLUGIN_INSTALL", "").strip().lower() not in ("", "0", "false", "no"):
|
||||||
|
log.info("Skipping requirement install for plugin %r (SLOPSMITH_SKIP_PLUGIN_INSTALL set)", plugin_id)
|
||||||
|
return True
|
||||||
|
|
||||||
_PIP_TARGET.mkdir(parents=True, exist_ok=True)
|
_PIP_TARGET.mkdir(parents=True, exist_ok=True)
|
||||||
pip_target = str(_PIP_TARGET)
|
pip_target = str(_PIP_TARGET)
|
||||||
|
|
||||||
@@ -946,9 +956,32 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
|
|||||||
# (PYTHONHASHSEED), so the marker would never match on restart and
|
# (PYTHONHASHSEED), so the marker would never match on restart and
|
||||||
# pip would re-resolve every plugin's requirements on every boot.
|
# pip would re-resolve every plugin's requirements on every boot.
|
||||||
marker = _PIP_TARGET / f".installed_{plugin_id}"
|
marker = _PIP_TARGET / f".installed_{plugin_id}"
|
||||||
|
fail_marker = _PIP_TARGET / f".failed_{plugin_id}"
|
||||||
req_hash = hashlib.sha256(req_file.read_bytes()).hexdigest()
|
req_hash = hashlib.sha256(req_file.read_bytes()).hexdigest()
|
||||||
if marker.exists() and marker.read_text().strip() == req_hash:
|
|
||||||
|
def _marker_matches(m):
|
||||||
|
# Tolerate an unreadable/transiently-broken marker (permissions, I/O):
|
||||||
|
# treat it as "no match" and fall through to a normal install attempt
|
||||||
|
# rather than letting read_text() raise out of this function.
|
||||||
|
try:
|
||||||
|
return m.exists() and m.read_text().strip() == req_hash
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if _marker_matches(marker):
|
||||||
return True # Already installed, same requirements
|
return True # Already installed, same requirements
|
||||||
|
# A previous install of these exact requirements already failed. Don't
|
||||||
|
# re-attempt on every boot: that re-blocks startup for the full pip timeout
|
||||||
|
# each launch. Retry only when requirements.txt changes (new hash) or the
|
||||||
|
# .failed_ marker is cleared.
|
||||||
|
if _marker_matches(fail_marker):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _record_failure():
|
||||||
|
try:
|
||||||
|
fail_marker.write_text(req_hash)
|
||||||
|
except OSError:
|
||||||
|
pass # read-only target: nothing to persist
|
||||||
|
|
||||||
log.info("Installing requirements for plugin %r (this can take a while for large deps)...", plugin_id)
|
log.info("Installing requirements for plugin %r (this can take a while for large deps)...", plugin_id)
|
||||||
try:
|
try:
|
||||||
@@ -960,7 +993,20 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
|
|||||||
capture_output=True, text=True, timeout=1800,
|
capture_output=True, text=True, timeout=1800,
|
||||||
)
|
)
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
marker.write_text(req_hash)
|
# Persisting markers is best-effort and must NOT fall through to the
|
||||||
|
# outer `except` (which would call _record_failure() and make a
|
||||||
|
# SUCCESSFUL install look like a sticky failure). The two writes are
|
||||||
|
# independent: clearing a stale .failed_ marker must still happen
|
||||||
|
# even if writing the success marker fails — otherwise a real
|
||||||
|
# success would stay recorded as a failure on the next boot.
|
||||||
|
try:
|
||||||
|
marker.write_text(req_hash)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
fail_marker.unlink() # clear any stale failure record
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
log.info("Requirements installed for plugin %r", plugin_id)
|
log.info("Requirements installed for plugin %r", plugin_id)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -974,6 +1020,7 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log.warning("Plugin %r: failed to install requirements: %s", plugin_id, result.stderr[:300])
|
log.warning("Plugin %r: failed to install requirements: %s", plugin_id, result.stderr[:300])
|
||||||
|
_record_failure()
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
err_lower = str(e).lower()
|
err_lower = str(e).lower()
|
||||||
@@ -986,6 +1033,7 @@ def _install_requirements(plugin_dir: Path, plugin_id: str):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log.warning("Plugin %r: error installing requirements: %s", plugin_id, e)
|
log.warning("Plugin %r: error installing requirements: %s", plugin_id, e)
|
||||||
|
_record_failure()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -382,7 +382,7 @@
|
|||||||
if (attributionOnly && attributionOnly.has(String(surface || ''))) return null;
|
if (attributionOnly && attributionOnly.has(String(surface || ''))) return null;
|
||||||
const mapped = (LEGACY_SURFACE_ENDPOINTS[String(capability || '')] || {})[String(surface || '')];
|
const mapped = (LEGACY_SURFACE_ENDPOINTS[String(capability || '')] || {})[String(surface || '')];
|
||||||
if (mapped) return mapped;
|
if (mapped) return mapped;
|
||||||
return null;
|
return { type: 'command', label: String(surface || '').trim() };
|
||||||
}
|
}
|
||||||
|
|
||||||
function shimEndpoints(shims, type, onlyHits) {
|
function shimEndpoints(shims, type, onlyHits) {
|
||||||
|
|||||||
@@ -542,35 +542,6 @@ test('capability inspector links library legacy command surfaces to canonical en
|
|||||||
assert.match(libraryContent, /title="2 participants"/);
|
assert.match(libraryContent, /title="2 participants"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('capability inspector does not render descriptive compatibility shims as commands', () => {
|
|
||||||
const snapshot = {
|
|
||||||
pipelines: [
|
|
||||||
{ name: 'visualization', review: { lifecycle: 'active', label: 'Active contract', tone: 'clean', summary: 'Visualization surface.' }, participants: [
|
|
||||||
{ pluginId: 'core.visualization', roles: ['owner'], commands: ['inspect', 'list-providers', 'select-renderer', 'clear-renderer'], operations: ['renderer.create', 'renderer.destroy'], events: [], runtime: true, availability: 'available', ownership: 'multi-provider', safety: 'safe' },
|
|
||||||
], conflicts: [] },
|
|
||||||
],
|
|
||||||
participants: [{ pluginId: 'core.visualization' }],
|
|
||||||
compatibilityShims: [
|
|
||||||
{ shimId: 'visualization:window.slopsmithViz_*', source: 'core.visualization', capability: 'visualization', legacySurface: 'window.slopsmithViz_* factory globals', status: 'used', hitCount: 1 },
|
|
||||||
{ shimId: 'visualization:type-visualization-manifest', source: 'core.visualization', capability: 'visualization', legacySurface: 'plugin.json type: "visualization"', status: 'used', hitCount: 1 },
|
|
||||||
],
|
|
||||||
expectedCompatibilityShims: [],
|
|
||||||
};
|
|
||||||
const { window, elements } = loadInspector(snapshot);
|
|
||||||
const filter = elements.get('capability-inspector-filter');
|
|
||||||
|
|
||||||
filter.value = 'visualization';
|
|
||||||
window.__slopsmithCapabilityInspector.render();
|
|
||||||
const content = elements.get('capability-inspector-content').innerHTML;
|
|
||||||
|
|
||||||
assert.match(content, /data-capability-node="command:inspect"/);
|
|
||||||
assert.match(content, /data-capability-node="operation:renderer\.create"/);
|
|
||||||
assert.doesNotMatch(content, /data-capability-node="command:window\.slopsmithViz_\* factory globals"/);
|
|
||||||
assert.doesNotMatch(content, /data-capability-node="command:plugin\.json type: "visualization""/);
|
|
||||||
assert.doesNotMatch(content, /data-link-kind="shimmed"[^>]*>window\.slopsmithViz_\* factory globals<\/span>/);
|
|
||||||
assert.doesNotMatch(content, /data-link-kind="shimmed"[^>]*>plugin\.json type: (?:"|")visualization(?:"|")<\/span>/);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('capability inspector clears provider hover without a graph hover target', () => {
|
test('capability inspector clears provider hover without a graph hover target', () => {
|
||||||
const snapshot = {
|
const snapshot = {
|
||||||
pipelines: [
|
pipelines: [
|
||||||
|
|||||||
Reference in New Issue
Block a user