AI-LSC v3.3.0 - MoE QA refactoring
This commit is contained in:
parent
289324317a
commit
918eab8cdd
24
CHANGES.md
24
CHANGES.md
|
|
@ -221,8 +221,9 @@ become L9; IaC and cluster automation land in L10.
|
||||||
- `ui/pages/db_manager.py` **repaired and migrated**: the interim
|
- `ui/pages/db_manager.py` **repaired and migrated**: the interim
|
||||||
migration had truncated the file from 1300 to 222 lines (syntax error
|
migration had truncated the file from 1300 to 222 lines (syntax error
|
||||||
at the CATEGORY_MAP tail; the entire DB-manager dialog/table
|
at the CATEGORY_MAP tail; the entire DB-manager dialog/table
|
||||||
implementation was lost). Restored in full from the v3.1.1b routing
|
implementation was lost). The canonical 1.3 k-line implementation is
|
||||||
tarball, then its CATEGORY_MAP was migrated to the 10-layer taxonomy.
|
re-established in-tree, with its CATEGORY_MAP migrated to the 10-layer
|
||||||
|
taxonomy.
|
||||||
- Categorisation cascade extended to 221 categories: the master map
|
- Categorisation cascade extended to 221 categories: the master map
|
||||||
(105) + 43 categories for the classified tools + 73 preserved
|
(105) + 43 categories for the classified tools + 73 preserved
|
||||||
categories from the v3.1.1b cascade, translated to the 10-layer
|
categories from the v3.1.1b cascade, translated to the 10-layer
|
||||||
|
|
@ -251,13 +252,14 @@ NAV_LAYER_ORDER matches the taxonomy; guardrails clean; headless
|
||||||
package import + `RegistryManager` first-boot/second-run bootstrap
|
package import + `RegistryManager` first-boot/second-run bootstrap
|
||||||
verified (186 tools seeded to ecosystem.json, structural sync clean).
|
verified (186 tools seeded to ecosystem.json, structural sync clean).
|
||||||
|
|
||||||
## v3.1.1b — taxonomy re-org: Routing restored (11 layers), canonical 24-dir /mnt/AI layout
|
## v3.1.1b — taxonomy re-org: Routing promoted to a first-class layer (11 layers), canonical 24-dir /mnt/AI layout
|
||||||
|
|
||||||
Restores the Routing layer that was lost when the 13-layer model was
|
Promotes the Routing layer to a first-class stratum — distinct from
|
||||||
compressed to 10 (both "L6 AI Endpoints" and "L11 Intelligent Routing"
|
Orchestrators — between Engines (who serve weights) and Orchestrators
|
||||||
had been folded into Orchestrators), fixes the mis-categorized tools,
|
(who build agent workflows on the OpenAI-compat endpoint Routing
|
||||||
and aligns the backend to the revised canonical `/mnt/AI/` directory
|
provides). Aligns the backend to the revised canonical `/mnt/AI/`
|
||||||
tree. No tool count change (185 tools); validator still reports 0 errors.
|
directory tree. No tool count change (185 tools); validator still
|
||||||
|
reports 0 errors.
|
||||||
|
|
||||||
### New layer ladder (10 → 11)
|
### New layer ladder (10 → 11)
|
||||||
|
|
||||||
|
|
@ -266,7 +268,7 @@ L1 Host Platform L7 Security
|
||||||
L2 Development Env L8 Observability
|
L2 Development Env L8 Observability
|
||||||
L3 GPU Runtimes L9 User Interfaces
|
L3 GPU Runtimes L9 User Interfaces
|
||||||
L4 Engines L10 DevOps
|
L4 Engines L10 DevOps
|
||||||
L5 Routing (restored) L11 Knowledge Management
|
L5 Routing L11 Knowledge Management
|
||||||
L6 Orchestrators
|
L6 Orchestrators
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -318,8 +320,8 @@ so the category cascade cannot silently flip it back to DevOps.
|
||||||
`agno`, `hermes_dashboard_page`, `mnemo_cortex`, `everos_memory`,
|
`agno`, `hermes_dashboard_page`, `mnemo_cortex`, `everos_memory`,
|
||||||
`langflow`, `opensandbox`). The stack logic editor now groups tools
|
`langflow`, `opensandbox`). The stack logic editor now groups tools
|
||||||
identically to the Infrastructure pages.
|
identically to the Infrastructure pages.
|
||||||
- Stale section comments annotated: `L6: AI Endpoints` → the restored
|
- Stale section comments annotated: `L6: AI Endpoints` → the L5 Routing
|
||||||
Routing layer; `L10: Intelligent Routing` → folded into Orchestrators.
|
layer; `L10: Intelligent Routing` → folded into Orchestrators.
|
||||||
|
|
||||||
### Canonical `/mnt/AI/` layout (26 → 24 dirs)
|
### Canonical `/mnt/AI/` layout (26 → 24 dirs)
|
||||||
|
|
||||||
|
|
|
||||||
5
LICENSE
5
LICENSE
|
|
@ -1,7 +1,8 @@
|
||||||
========================================================================
|
========================================================================
|
||||||
PROJECT: AI-LSC (AI Local Stack Control)
|
PROJECT: AI-LSC (AI Local Stack Control)
|
||||||
COPYRIGHT: Copyright (C) 2026 dcos.net
|
AUTHOR: Jeremy Anderson <info@dcos.net>
|
||||||
HOMEPAGE: https://git.dcos.net/dcosnet/ai-lsc/
|
COPYRIGHT: Copyright (C) 2026 Jeremy Anderson / dcos.net
|
||||||
|
HOMEPAGE: https://dcos.net
|
||||||
REPOSITORY: https://git.dcos.net/dcosnet/ai-lsc/
|
REPOSITORY: https://git.dcos.net/dcosnet/ai-lsc/
|
||||||
LICENSE: GNU Affero General Public License v3.0 (AGPL-3.0)
|
LICENSE: GNU Affero General Public License v3.0 (AGPL-3.0)
|
||||||
========================================================================
|
========================================================================
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
# AI-LSC MoE QA Pass — Final Report
|
||||||
|
|
||||||
|
**Source:** `ai-lsc-main(1).tar.gz`
|
||||||
|
**Date:** 2026-08-31
|
||||||
|
**Pass scope:** Surgical cleanup (one more pass after v3.1 critique + v3.2 taxonomy migration).
|
||||||
|
**MoE panel:** Senior QA analyst · Senior Linux engineer · Senior architect · Senior admin · DevOps PM.
|
||||||
|
|
||||||
|
## 1. Standards referenced
|
||||||
|
|
||||||
|
| Standard | Application |
|
||||||
|
|---|---|
|
||||||
|
| PEP 868 / PEP 8 | Type annotations, line length ≤100, import ordering. |
|
||||||
|
| POSIX sh / bash | `set -euo pipefail`, no bashisms in sourced contexts. |
|
||||||
|
| SEI CERT Python | Input validation, no `shell=True`, atomic JSON writes preserved. |
|
||||||
|
| MISRA-C spirit | Single-exit, dispatch tables over flag-of-flags, early-return step-down. |
|
||||||
|
| Unix philosophy | Each function does one thing; forks of choices resolve via step-down. |
|
||||||
|
|
||||||
|
## 2. Wording sweep — "restored / brought back / related logic" eliminated
|
||||||
|
|
||||||
|
All instances rewritten as decisive statements. Source files touched:
|
||||||
|
|
||||||
|
| File | Line(s) | Change |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/ai_lsc/registry/layers/routing.py` | docstring | "Restored layer (v3.1.1b)" → "L5 (Intelligent API Routers & Proxies) holds …"; added source-of-truth note. |
|
||||||
|
| `TODO.md` | 6 | "restored in full from the v3.1.1b routing tarball" → "the canonical 1.3 k-line implementation now lives in-tree". |
|
||||||
|
| `scripts/apply_10layer_taxonomy.py` | 201, 668, 757 | "restored from the routing tarball" → "canonical 124-category cascade"; "the restored Routing layer" → "the L5 Routing layer". |
|
||||||
|
| `CHANGES.md` | 224, 254, 269, 321 | "Restored in full from the v3.1.1b routing tarball" → "canonical 1.3 k-line implementation is re-established in-tree"; section header "Routing restored" → "Routing promoted to a first-class layer"; "(restored)" ladder annotation removed. |
|
||||||
|
| `whatremains.txt` | 9 | "the restored installer cmds" → "the canonical installer cmds". |
|
||||||
|
| `docs/ADR-003-workspace-tab.md` | 67 | "The empty-state placeholder is restored" → "the empty-state placeholder is shown again". |
|
||||||
|
|
||||||
|
Verification: `grep -rE '\b(restored|brought back|related logic)\b'` returns **0 matches**.
|
||||||
|
|
||||||
|
## 3. Author metadata
|
||||||
|
|
||||||
|
| File | Field | Value |
|
||||||
|
|---|---|---|
|
||||||
|
| `pyproject.toml` | `authors`, `maintainers` | Jeremy Anderson `<info@dcos.net>` |
|
||||||
|
| `README.md` | header | Author + email + https://dcos.net link |
|
||||||
|
| `README.md` | clone URL | `https://github.com/dcos-net/ai-lsc.git` |
|
||||||
|
| `LICENSE` | header | AUTHOR + COPYRIGHT + HOMEPAGE = `dcos.net` |
|
||||||
|
|
||||||
|
## 4. Code patterns — nested ifs → arrays/dispatch tables
|
||||||
|
|
||||||
|
### 4.1 `src/ai_lsc/manifest/support.py` — `build_system_context`
|
||||||
|
|
||||||
|
Replaced 6 cascaded `if field:` blocks with a single `_FIELDS` tuple + comprehension:
|
||||||
|
|
||||||
|
```python
|
||||||
|
_FIELDS: tuple[tuple[str, str], ...] = (
|
||||||
|
("description", "Description"),
|
||||||
|
("language", "Language"),
|
||||||
|
("entry_point", "Entry Point"),
|
||||||
|
("architecture", "Architecture"),
|
||||||
|
("environment_notes", "Environment"),
|
||||||
|
)
|
||||||
|
parts = [f"Project: {project}"]
|
||||||
|
parts.extend(
|
||||||
|
f"{label}: {manifest.get(key)}"
|
||||||
|
for key, label in _FIELDS
|
||||||
|
if manifest.get(key)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Added a "Source-of-truth boundary" docstring noting the manifest is a derived view; the registry layer files remain authoritative.
|
||||||
|
|
||||||
|
### 4.2 `src/ai_lsc/manifest/support.py` — `resolve_context_files`
|
||||||
|
|
||||||
|
Replaced outer-for + inner-for + if with a single list comprehension (one pass over patterns → files → filter).
|
||||||
|
|
||||||
|
### 4.3 `src/ai_lsc/ui/pages/chatbot_console.py` — `_build_payload_history` (M-40 / L-09)
|
||||||
|
|
||||||
|
Replaced the 3-way nested ternary
|
||||||
|
`parts[0] + "\n\n" + parts[1] if len(parts) == 2 else (parts[0] if parts else "")`
|
||||||
|
with the flat `"\n\n".join(parts)`. Behavior preserved (empty parts → empty string).
|
||||||
|
|
||||||
|
### 4.4 `src/ai_lsc/ui/pages/chatbot_console.py` — HTML bubble builder (M-22)
|
||||||
|
|
||||||
|
Extracted `_render_bubble(msg)` helper. Replaced two nested ternaries (`style_key`, `label_color`) with dispatch tables `_identity_color` and `_identity_label`. The main loop is now a one-liner:
|
||||||
|
|
||||||
|
```python
|
||||||
|
html += "".join(_render_bubble(m) for m in self.chat_messages)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Unix philosophy — step-down (early return) refactors
|
||||||
|
|
||||||
|
### 5.1 `bootstrap.sh` — base-dir detection (lines 70-100)
|
||||||
|
|
||||||
|
The 4-level nested `if/elif/else` (root → prompt → sudo/alt-base/error) was inverted to a step-down `if/elif/else` where each branch resolves one situation and exits the block. Removed one nesting level.
|
||||||
|
|
||||||
|
### 5.2 `src/ai_lsc/runtime/installer.py` — `install_git` / `install_git_node`
|
||||||
|
|
||||||
|
Both methods had a 3-way fork (pull / re-clone-on-failure / clone-non-git-dir / fresh-clone) with duplicated `shutil.move + makedirs + git clone` code across branches. Extracted:
|
||||||
|
|
||||||
|
- `_backup_existing(dest) -> str` — single-purpose backup helper.
|
||||||
|
- `_clone_fresh(pkg, dest) -> None` — single-purpose clone helper.
|
||||||
|
|
||||||
|
Each method body is now a clean step-down: `if has_git_dir: try pull / except: backup+clone` → `elif exists: backup+clone` → `else: clone`. Behavior identical; ~30 lines of duplication removed.
|
||||||
|
|
||||||
|
### 5.3 `src/ai_lsc/registry/openengineer/importer.py` — directory scan
|
||||||
|
|
||||||
|
The two `for md_file in sorted(...)` blocks (subdir scan + root-level scan) with overlapping filter logic were merged into a single `_scan_dir()` helper with keyword-only parameters (`overrides`, `drop_unknown`, `skip`). The two caller sites now express only what differs (subdir scan keeps unknowns; root scan drops them and skips meta files).
|
||||||
|
|
||||||
|
## 6. Docs vs source — source-of-truth boundary
|
||||||
|
|
||||||
|
The manifest module (`src/ai_lsc/manifest/support.py`) and the routing layer file (`src/ai_lsc/registry/layers/routing.py`) now both carry explicit "source of truth" docstrings:
|
||||||
|
|
||||||
|
> The manifest file itself is an optional convenience; if absent, callers fall back to defaults — the registry layer files in `ai_lsc.registry.layers` remain the authoritative source for tool definitions.
|
||||||
|
|
||||||
|
No code-behavior change; the comment makes the architectural boundary explicit so a future maintainer cannot accidentally invert it.
|
||||||
|
|
||||||
|
## 7. Verification
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
|---|---|
|
||||||
|
| AST-parse 100 Python files | **0 failures** |
|
||||||
|
| `bash -n` on bootstrap.sh + run.sh | **0 failures** |
|
||||||
|
| Refactored modules import cleanly | **OK** |
|
||||||
|
| `build_system_context` smoke test | **OK** (dispatch table produces identical output) |
|
||||||
|
| `resolve_context_files` smoke test | **OK** (returns same file list) |
|
||||||
|
| Wording sweep grep | **0 matches** for `restored|brought back|related logic` |
|
||||||
|
|
||||||
|
## 8. Out-of-scope (preserved per user policy)
|
||||||
|
|
||||||
|
- `curl|sh` installers (Ollama, Grafana Alloy, Meilisearch) — C-05 policy (see `whatremains.txt`).
|
||||||
|
- Layer-file `filesystem` backfill (L-18) — mechanical, ~10 min/layer.
|
||||||
|
- `defaults.py` 3700-line split (L-14) — conflicts with layer-file decomposition.
|
||||||
|
- Pre-existing unused imports `JCL_FILE_NAME` (support.py) and `Path` (installer.py) — flagged by `pyflakes`-equivalent scan but predate this pass; left untouched to avoid scope creep.
|
||||||
|
|
||||||
|
## 9. Tarball
|
||||||
|
|
||||||
|
Packaged as `ai-lsc-moe-qa-pass.tar.gz` in `/home/z/my-project/download/`. Contains the full project tree post-cleanup.
|
||||||
|
|
@ -6,10 +6,11 @@
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>v3.1.1 — Codename: Ankh of Jah (local-coder-mesh build)</strong><br>
|
<strong>v3.1.1 — Codename: Ankh of Jah (local-coder-mesh build)</strong><br>
|
||||||
|
Author: <a href="https://dcos.net">Jeremy Anderson</a> <<a href="mailto:info@dcos.net">info@dcos.net</a>><br>
|
||||||
|
Web: <a href="https://dcos.net">https://dcos.net</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<p align="center"><em>This build includes the local-coder-mesh integration — see <a href="CHANGES.md">CHANGES.md</a> for the full list of changes vs upstream v3.1.</em></p>
|
<p align="center"><em>This build includes the local-coder-mesh integration — see <a href="CHANGES.md">CHANGES.md</a> for the full list of changes vs upstream v3.1.</em></p>
|
||||||
<a href="http://dcos.net">http://dcos.net</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
A PySide6 desktop application for orchestrating local AI/ML tool stacks across a 10-layer architecture.
|
A PySide6 desktop application for orchestrating local AI/ML tool stacks across a 10-layer architecture.
|
||||||
|
|
@ -238,7 +239,7 @@ ai_lsc/
|
||||||
### Quick Install
|
### Quick Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/your-username/ai-lsc.git
|
git clone https://github.com/dcos-net/ai-lsc.git
|
||||||
cd ai-lsc
|
cd ai-lsc
|
||||||
pip install -e .
|
pip install -e .
|
||||||
```
|
```
|
||||||
|
|
|
||||||
2
TODO.md
2
TODO.md
|
|
@ -3,7 +3,7 @@
|
||||||
## Recent progress (v3.2 — 10-Layer Systems Architecture)
|
## Recent progress (v3.2 — 10-Layer Systems Architecture)
|
||||||
|
|
||||||
- ✅ Completed the 10-layer taxonomy migration that was started on top of v3.1.1b: `defaults.py` (108-tool seed), all 11 modular layer files (186 tools), `stack/connections.py` (92 static layers + 5 dynamic loop lookups), CATEGORY_MAP cascade (221 categories), validator level range, README/quickstart/docs. See [CHANGES.md](CHANGES.md) → v3.2.
|
- ✅ Completed the 10-layer taxonomy migration that was started on top of v3.1.1b: `defaults.py` (108-tool seed), all 11 modular layer files (186 tools), `stack/connections.py` (92 static layers + 5 dynamic loop lookups), CATEGORY_MAP cascade (221 categories), validator level range, README/quickstart/docs. See [CHANGES.md](CHANGES.md) → v3.2.
|
||||||
- ✅ Repaired `ui/pages/db_manager.py`: the interim migration had truncated it to 222 lines (syntax error + lost implementation); restored in full from the v3.1.1b routing tarball and migrated its CATEGORY_MAP.
|
- ✅ Repaired `ui/pages/db_manager.py`: the interim migration had truncated it to 222 lines (syntax error + lost implementation); the canonical 1.3 k-line implementation now lives in-tree and its CATEGORY_MAP is migrated to the 10-layer taxonomy.
|
||||||
- ✅ Reconciled `kanban` into the layer files (was defaults-only) — merged registry is now 186 tools.
|
- ✅ Reconciled `kanban` into the layer files (was defaults-only) — merged registry is now 186 tools.
|
||||||
- ✅ Preserved all operational registry metadata (installers, launchers, deps, flags, licenses, filesystem blocks) that the master-target defaults rewrite had stripped; curl|sh policy markers untouched (C-05).
|
- ✅ Preserved all operational registry metadata (installers, launchers, deps, flags, licenses, filesystem blocks) that the master-target defaults rewrite had stripped; curl|sh policy markers untouched (C-05).
|
||||||
- ✅ `open_webui` vs `openwebui` orphan gap: confirmed moot — only `openwebui` exists in the registry and it has a STACK_WIRINGS entry.
|
- ✅ `open_webui` vs `openwebui` orphan gap: confirmed moot — only `openwebui` exists in the registry and it has a STACK_WIRINGS entry.
|
||||||
|
|
|
||||||
30
bootstrap.sh
30
bootstrap.sh
|
|
@ -68,34 +68,36 @@ if command -v pipx &>/dev/null && pipx list 2>/dev/null | grep -q "ai-lsc"; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Ensure AI_BASE exists ─────────────────────────────────────
|
# ── Ensure AI_BASE exists ─────────────────────────────────────
|
||||||
if [ ! -d "$AI_BASE" ]; then
|
# Step-down (early-return) logic per Unix philosophy: each branch
|
||||||
if [ "$(id -u)" -eq 0 ]; then
|
# resolves the situation and falls through; the bottom of the block
|
||||||
|
# is the success path.
|
||||||
|
if [ -d "$AI_BASE" ]; then
|
||||||
|
: # already present — nothing to do
|
||||||
|
elif [ "$(id -u)" -eq 0 ]; then
|
||||||
mkdir -p "$AI_BASE"
|
mkdir -p "$AI_BASE"
|
||||||
info "Created ${AI_BASE} (running as root)"
|
info "Created ${AI_BASE} (running as root)"
|
||||||
else
|
else
|
||||||
|
# Non-root path: prompt for action, escalate if possible.
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${YELLOW}${AI_BASE} does not exist.${NC}"
|
echo -e "${YELLOW}${AI_BASE} does not exist.${NC}"
|
||||||
echo " This is the managed working directory for all AI tools."
|
echo " This is the managed working directory for all AI tools."
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Create ${AI_BASE} now? [Y/n] " -n 1 -r
|
read -p "Create ${AI_BASE} now? [Y/n] " -n 1 -r
|
||||||
echo
|
echo
|
||||||
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
# User declined the canonical path → fall through to alternative.
|
||||||
if command -v sudo &>/dev/null; then
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
sudo mkdir -p "$AI_BASE"
|
|
||||||
sudo chown "$(id -u):$(id -g)" "$AI_BASE"
|
|
||||||
info "Created ${AI_BASE}"
|
|
||||||
else
|
|
||||||
error "Need sudo/root to create ${AI_BASE}. Create it manually and re-run."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
read -p "Alternative base directory? [${SCRIPT_DIR}/ai-stack] " ALT_BASE
|
read -p "Alternative base directory? [${SCRIPT_DIR}/ai-stack] " ALT_BASE
|
||||||
ALT_BASE="${ALT_BASE:-${SCRIPT_DIR}/ai-stack}"
|
ALT_BASE="${ALT_BASE:-${SCRIPT_DIR}/ai-stack}"
|
||||||
mkdir -p "$ALT_BASE"
|
mkdir -p "$ALT_BASE"
|
||||||
warn "Using ${ALT_BASE}"
|
warn "Using ${ALT_BASE}"
|
||||||
AI_BASE="$ALT_BASE"
|
AI_BASE="$ALT_BASE"
|
||||||
export AI_LSC_BASE_DIR="$AI_BASE"
|
export AI_LSC_BASE_DIR="$AI_BASE"
|
||||||
fi
|
elif command -v sudo &>/dev/null; then
|
||||||
|
sudo mkdir -p "$AI_BASE"
|
||||||
|
sudo chown "$(id -u):$(id -g)" "$AI_BASE"
|
||||||
|
info "Created ${AI_BASE}"
|
||||||
|
else
|
||||||
|
error "Need sudo/root to create ${AI_BASE}. Create it manually and re-run."
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ The emoji encoding lets the user scan the tab bar at a glance and see what kind
|
||||||
|
|
||||||
### Sub-tab closing
|
### Sub-tab closing
|
||||||
|
|
||||||
Sub-tabs are closable via the standard × button. Closing a sub-tab calls `stop_polling()` on the page (for CLI tools) but does **NOT** stop the underlying tool — that's the user's call from the Stack Editor. The empty-state placeholder is restored when all sub-tabs are closed.
|
Sub-tabs are closable via the standard × button. Closing a sub-tab calls `stop_polling()` on the page (for CLI tools) but does **NOT** stop the underlying tool — that's the user's call from the Stack Editor. When all sub-tabs are closed the empty-state placeholder is shown again.
|
||||||
|
|
||||||
## Refresh model
|
## Refresh model
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ readme = "README.md"
|
||||||
license = {text = "AGPL-3.0-or-later"}
|
license = {text = "AGPL-3.0-or-later"}
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "AI-LSC Contributors"},
|
{name = "Jeremy Anderson", email = "info@dcos.net"},
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "Jeremy Anderson", email = "info@dcos.net"},
|
||||||
]
|
]
|
||||||
keywords = [
|
keywords = [
|
||||||
"ai", "llm", "local-ai", "stack-management", "pyside6",
|
"ai", "llm", "local-ai", "stack-management", "pyside6",
|
||||||
|
|
|
||||||
|
|
@ -198,10 +198,10 @@ EXTRA_CATEGORY_LAYERS: dict[str, str] = {
|
||||||
"Observability": "Human Interface & System Operations",
|
"Observability": "Human Interface & System Operations",
|
||||||
}
|
}
|
||||||
|
|
||||||
# The v3.1.1b db_manager (restored from the routing tarball) carries a
|
# The v3.1.1b db_manager (canonical 124-category cascade) carries a
|
||||||
# richer 124-category cascade than the master target's 103-category map.
|
# richer 124-category cascade than the master target's 103-category map.
|
||||||
# These alt-only categories are preserved and translated to the 10-layer
|
# These alt-only categories are translated to the 10-layer taxonomy so
|
||||||
# taxonomy so the categorisation cascade keeps its full coverage.
|
# the categorisation cascade keeps its full coverage.
|
||||||
ALT_CATEGORY_TRANSLATIONS: dict[str, str] = {
|
ALT_CATEGORY_TRANSLATIONS: dict[str, str] = {
|
||||||
"AI Assistant Platform": "Human Interface & System Operations",
|
"AI Assistant Platform": "Human Interface & System Operations",
|
||||||
"AI Augmentation": "Multi-Agent Orchestration Runtimes",
|
"AI Augmentation": "Multi-Agent Orchestration Runtimes",
|
||||||
|
|
@ -665,7 +665,7 @@ SECTION_RENAMES = [
|
||||||
"# L3: GPU Runtime → Layer 3: GPU Acceleration & Optimization"),
|
"# L3: GPU Runtime → Layer 3: GPU Acceleration & Optimization"),
|
||||||
("# L4: Inference Engines",
|
("# L4: Inference Engines",
|
||||||
"# L4: Inference Engines → Layer 4: Local Inference Engines"),
|
"# L4: Inference Engines → Layer 4: Local Inference Engines"),
|
||||||
("# L6: AI Endpoints (→ the restored \"Routing\" layer in the 11-layer taxonomy)",
|
("# L6: AI Endpoints (→ the L5 \"Routing\" layer in the 11-layer taxonomy)",
|
||||||
"# L6: AI Endpoints → Layer 5: Intelligent API Routers & Proxies (10-layer taxonomy)"),
|
"# L6: AI Endpoints → Layer 5: Intelligent API Routers & Proxies (10-layer taxonomy)"),
|
||||||
("# L10: Intelligent Routing (folded into \"Orchestrators\" in the 11-layer taxonomy)",
|
("# L10: Intelligent Routing (folded into \"Orchestrators\" in the 11-layer taxonomy)",
|
||||||
"# L10: Intelligent Routing → Layer 5: Intelligent API Routers & Proxies (10-layer taxonomy)"),
|
"# L10: Intelligent Routing → Layer 5: Intelligent API Routers & Proxies (10-layer taxonomy)"),
|
||||||
|
|
@ -754,8 +754,8 @@ def category_map_lines(cmap: dict[str, dict]) -> str:
|
||||||
|
|
||||||
|
|
||||||
def stage_category_map() -> None:
|
def stage_category_map() -> None:
|
||||||
# read the current CATEGORY_MAP from the restored v3.1.1b db_manager
|
# Read the current CATEGORY_MAP from the canonical v3.1.1b db_manager
|
||||||
# (full 124-category cascade in the old 11-layer taxonomy)
|
# (full 124-category cascade in the old 11-layer taxonomy).
|
||||||
dbm = SRC / "ai_lsc" / "ui" / "pages" / "db_manager.py"
|
dbm = SRC / "ai_lsc" / "ui" / "pages" / "db_manager.py"
|
||||||
content = dbm.read_text(encoding="utf-8")
|
content = dbm.read_text(encoding="utf-8")
|
||||||
m = re.search(
|
m = re.search(
|
||||||
|
|
|
||||||
|
|
@ -76,26 +76,32 @@ class ManifestSupport:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_system_context(manifest: dict[str, Any]) -> str:
|
def build_system_context(manifest: dict[str, Any]) -> str:
|
||||||
"""Build a flat system-prompt text block from manifest data."""
|
"""Build a flat system-prompt text block from manifest data.
|
||||||
|
|
||||||
|
Source-of-truth boundary: this method renders a *derived* view of
|
||||||
|
the manifest dict. The manifest file itself is an optional
|
||||||
|
convenience; if absent, callers fall back to defaults — the
|
||||||
|
registry layer files in :mod:`ai_lsc.registry.layers` remain
|
||||||
|
the authoritative source for tool definitions.
|
||||||
|
"""
|
||||||
|
# (manifest_key, label) pairs in display order. Each entry whose
|
||||||
|
# manifest value is truthy becomes one line in the prompt.
|
||||||
|
_FIELDS: tuple[tuple[str, str], ...] = (
|
||||||
|
("description", "Description"),
|
||||||
|
("language", "Language"),
|
||||||
|
("entry_point", "Entry Point"),
|
||||||
|
("architecture", "Architecture"),
|
||||||
|
("environment_notes", "Environment"),
|
||||||
|
)
|
||||||
project = manifest.get("project", "Unknown Project")
|
project = manifest.get("project", "Unknown Project")
|
||||||
description = manifest.get("description", "")
|
|
||||||
language = manifest.get("language", "")
|
|
||||||
entry = manifest.get("entry_point", "")
|
|
||||||
architecture = manifest.get("architecture", "")
|
|
||||||
environment = manifest.get("environment_notes", "")
|
|
||||||
dependencies = manifest.get("dependencies", [])
|
dependencies = manifest.get("dependencies", [])
|
||||||
|
|
||||||
parts = [f"Project: {project}"]
|
parts = [f"Project: {project}"]
|
||||||
if description:
|
parts.extend(
|
||||||
parts.append(f"Description: {description}")
|
f"{label}: {manifest.get(key)}"
|
||||||
if language:
|
for key, label in _FIELDS
|
||||||
parts.append(f"Language: {language}")
|
if manifest.get(key)
|
||||||
if entry:
|
)
|
||||||
parts.append(f"Entry Point: {entry}")
|
|
||||||
if architecture:
|
|
||||||
parts.append(f"Architecture: {architecture}")
|
|
||||||
if environment:
|
|
||||||
parts.append(f"Environment: {environment}")
|
|
||||||
if dependencies:
|
if dependencies:
|
||||||
parts.append(f"Dependencies: {', '.join(dependencies)}")
|
parts.append(f"Dependencies: {', '.join(dependencies)}")
|
||||||
|
|
||||||
|
|
@ -110,15 +116,15 @@ class ManifestSupport:
|
||||||
base = Path(base_dir)
|
base = Path(base_dir)
|
||||||
patterns = manifest.get("context_files", [])
|
patterns = manifest.get("context_files", [])
|
||||||
exclude = set(manifest.get("exclude", []))
|
exclude = set(manifest.get("exclude", []))
|
||||||
files: list[str] = []
|
|
||||||
|
|
||||||
for pattern in patterns:
|
# Single-pass comprehension: expand every pattern, keep only
|
||||||
full_pattern = str(base / pattern)
|
# regular files whose path does not contain any excluded token.
|
||||||
matched = glob_mod.glob(full_pattern, recursive=True)
|
return [
|
||||||
for f in matched:
|
f
|
||||||
if Path(f).is_file() and not any(ex in f for ex in exclude):
|
for pattern in patterns
|
||||||
files.append(f)
|
for f in glob_mod.glob(str(base / pattern), recursive=True)
|
||||||
return files
|
if Path(f).is_file() and not any(ex in f for ex in exclude)
|
||||||
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_jcl(path: str | Path) -> list[dict[str, Any]]:
|
def load_jcl(path: str | Path) -> list[dict[str, Any]]:
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
"""Registry entries for the Routing layer (L5).
|
"""Registry entries for the Routing layer (L5).
|
||||||
|
|
||||||
Restored layer (v3.1.1b): model gateways, LLM proxies, request routers,
|
L5 (Intelligent API Routers & Proxies) holds the model-routing tier
|
||||||
mesh transport, and the model-routing tier that sits between the Engines
|
that sits between the Engines (who serve weights) and the Orchestrators
|
||||||
(who serve weights) and the Orchestrators (who build agent workflows on
|
(who build agent workflows on top of a single OpenAI-compatible
|
||||||
top of a single OpenAI-compatible endpoint). This re-unites the old
|
endpoint): model gateways, LLM proxies, request routers, mesh
|
||||||
13-layer model's "AI Endpoints" tier (LiteLLM, model routers, API
|
transport, and the mesh-aware clients that ride on it.
|
||||||
gateways) that had been folded into Orchestrators during the 13-to-10
|
|
||||||
reorg, plus the mesh-aware clients that ride on it.
|
|
||||||
|
|
||||||
This module is consumed by :mod:`ai_lsc.registry.loader`.
|
This module is consumed by :mod:`ai_lsc.registry.loader`. The loader
|
||||||
|
merges by tool, not by filename, so tools may be regrouped across
|
||||||
|
files in a future pass without a behavior change.
|
||||||
|
|
||||||
Structural fields (layer, level) follow the 10-Layer Systems
|
Source of truth: the entries below. The merged registry produced by
|
||||||
Architecture Taxonomy; tools may be regrouped across files in a
|
``loader.load_merged_registry()`` is a derived view; if any manifest
|
||||||
future pass — the loader merges by tool, not by filename.
|
or cached ``ecosystem.json`` disagrees, the source in this file wins.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
TOOLS: dict[str, dict] = {
|
TOOLS: dict[str, dict] = {
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,12 @@ _OE_CONTENT_DIRS: list[str] = [
|
||||||
# File extensions we attempt to parse.
|
# File extensions we attempt to parse.
|
||||||
_OE_PARSE_EXTENSIONS: set[str] = {".md", ".markdown", ".txt"}
|
_OE_PARSE_EXTENSIONS: set[str] = {".md", ".markdown", ".txt"}
|
||||||
|
|
||||||
|
# Root-level files that are repo meta, not OE content.
|
||||||
|
_OE_ROOT_SKIP_NAMES: frozenset[str] = frozenset({
|
||||||
|
"README.md", "CONTRIBUTING.md", "CHARTER.md",
|
||||||
|
"LICENSE", "ROADMAP.md",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class OpenEngineerImporter:
|
class OpenEngineerImporter:
|
||||||
"""Import Open Engineer files into StandardTemplate objects.
|
"""Import Open Engineer files into StandardTemplate objects.
|
||||||
|
|
@ -164,51 +170,53 @@ class OpenEngineerImporter:
|
||||||
overrides = stack_config_overrides or {}
|
overrides = stack_config_overrides or {}
|
||||||
templates: list[StandardTemplate] = []
|
templates: list[StandardTemplate] = []
|
||||||
|
|
||||||
# Scan known OE content directories
|
# Pass 1 — known OE content subdirs (templates inferred from OE fields).
|
||||||
for subdir_name in _OE_CONTENT_DIRS:
|
for subdir_name in _OE_CONTENT_DIRS:
|
||||||
subdir = root / subdir_name
|
subdir = root / subdir_name
|
||||||
if not subdir.is_dir():
|
if not subdir.is_dir():
|
||||||
continue
|
continue
|
||||||
|
templates.extend(self._scan_dir(subdir, overrides=overrides, drop_unknown=False))
|
||||||
|
|
||||||
|
# Pass 2 — root-level standalone OE files (drop unknowns so README/LICENSE/etc.
|
||||||
|
# do not pollute the import). The _OE_ROOT_SKIP_NAMES filter applies here only.
|
||||||
|
templates.extend(self._scan_dir(root, overrides=None, drop_unknown=True, skip=_OE_ROOT_SKIP_NAMES))
|
||||||
|
|
||||||
|
return sorted(templates, key=lambda t: t.source_file)
|
||||||
|
|
||||||
|
def _scan_dir(
|
||||||
|
self,
|
||||||
|
subdir: Path,
|
||||||
|
*,
|
||||||
|
overrides: dict[str, dict[str, Any]] | None = None,
|
||||||
|
drop_unknown: bool = False,
|
||||||
|
skip: frozenset[str] | None = None,
|
||||||
|
) -> list[StandardTemplate]:
|
||||||
|
"""Import every parseable file directly inside *subdir*.
|
||||||
|
|
||||||
|
Single-responsibility scan loop: yields one StandardTemplate per
|
||||||
|
parseable markdown file. Errors per file are swallowed so one bad
|
||||||
|
file does not abort the scan.
|
||||||
|
"""
|
||||||
|
results: list[StandardTemplate] = []
|
||||||
for md_file in sorted(subdir.iterdir()):
|
for md_file in sorted(subdir.iterdir()):
|
||||||
if md_file.suffix.lower() not in _OE_PARSE_EXTENSIONS:
|
|
||||||
continue
|
|
||||||
if md_file.name.startswith("."):
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
tpl_id = self._derive_template_id(
|
|
||||||
md_file, md_file.stem
|
|
||||||
)
|
|
||||||
override = overrides.get(tpl_id)
|
|
||||||
template = self.import_file(md_file, stack_config=override)
|
|
||||||
templates.append(template)
|
|
||||||
except Exception:
|
|
||||||
# Don't let one bad file stop the import
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Also scan root for standalone OE files
|
|
||||||
for md_file in sorted(root.iterdir()):
|
|
||||||
if not md_file.is_file():
|
if not md_file.is_file():
|
||||||
continue
|
continue
|
||||||
if md_file.suffix.lower() not in _OE_PARSE_EXTENSIONS:
|
if md_file.suffix.lower() not in _OE_PARSE_EXTENSIONS:
|
||||||
continue
|
continue
|
||||||
if md_file.name in {
|
|
||||||
"README.md", "CONTRIBUTING.md", "CHARTER.md",
|
|
||||||
"LICENSE", "ROADMAP.md",
|
|
||||||
}:
|
|
||||||
continue
|
|
||||||
if md_file.name.startswith("."):
|
if md_file.name.startswith("."):
|
||||||
continue
|
continue
|
||||||
|
if skip and md_file.name in skip:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
template = self.import_file(md_file)
|
tpl_id = self._derive_template_id(md_file, md_file.stem)
|
||||||
if template.source_type != "unknown":
|
override = (overrides or {}).get(tpl_id)
|
||||||
templates.append(template)
|
template = self.import_file(md_file, stack_config=override)
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
if drop_unknown and template.source_type == "unknown":
|
||||||
return sorted(templates, key=lambda t: t.source_file)
|
continue
|
||||||
|
results.append(template)
|
||||||
|
return results
|
||||||
|
|
||||||
# ── Bulk convert to AI-LSC format ──────────────────────────────
|
# ── Bulk convert to AI-LSC format ──────────────────────────────
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -574,6 +574,8 @@ class InstallerManager:
|
||||||
"""
|
"""
|
||||||
dest = os.path.join(self.tools_root, tool_id)
|
dest = os.path.join(self.tools_root, tool_id)
|
||||||
git_dir = os.path.join(dest, ".git")
|
git_dir = os.path.join(dest, ".git")
|
||||||
|
|
||||||
|
# Step-down: each branch resolves one situation and assigns msg.
|
||||||
if os.path.isdir(git_dir):
|
if os.path.isdir(git_dir):
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
|
|
@ -586,36 +588,22 @@ class InstallerManager:
|
||||||
"git pull failed for %s (%s); re-cloning fresh",
|
"git pull failed for %s (%s); re-cloning fresh",
|
||||||
tool_id, exc,
|
tool_id, exc,
|
||||||
)
|
)
|
||||||
backup = dest + ".bak." + str(int(time.time()))
|
backup = self._backup_existing(dest)
|
||||||
shutil.move(dest, backup)
|
self._clone_fresh(pkg, dest)
|
||||||
os.makedirs(dest, exist_ok=True)
|
|
||||||
subprocess.run(
|
|
||||||
["git", "clone", pkg, dest],
|
|
||||||
check=True, timeout=600,
|
|
||||||
)
|
|
||||||
msg = (
|
msg = (
|
||||||
f"Git source re-cloned (pull failed, "
|
f"Git source re-cloned (pull failed, "
|
||||||
f"old copy at {backup}): {dest}"
|
f"old copy at {backup}): {dest}"
|
||||||
)
|
)
|
||||||
elif os.path.exists(dest):
|
elif os.path.exists(dest):
|
||||||
# Dir exists but is not a git repo — back it up and clone.
|
# Dir exists but is not a git repo — back it up and clone.
|
||||||
backup = dest + ".bak." + str(int(time.time()))
|
backup = self._backup_existing(dest)
|
||||||
shutil.move(dest, backup)
|
self._clone_fresh(pkg, dest)
|
||||||
os.makedirs(dest, exist_ok=True)
|
|
||||||
subprocess.run(
|
|
||||||
["git", "clone", pkg, dest],
|
|
||||||
check=True, timeout=600,
|
|
||||||
)
|
|
||||||
msg = (
|
msg = (
|
||||||
f"Git source cloned (existing non-git dir backed up "
|
f"Git source cloned (existing non-git dir backed up "
|
||||||
f"at {backup}): {dest}"
|
f"at {backup}): {dest}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
os.makedirs(dest, exist_ok=True)
|
self._clone_fresh(pkg, dest)
|
||||||
subprocess.run(
|
|
||||||
["git", "clone", pkg, dest],
|
|
||||||
check=True, timeout=600,
|
|
||||||
)
|
|
||||||
msg = f"Git source cloned: {dest}"
|
msg = f"Git source cloned: {dest}"
|
||||||
|
|
||||||
if post_install:
|
if post_install:
|
||||||
|
|
@ -638,6 +626,9 @@ class InstallerManager:
|
||||||
"""
|
"""
|
||||||
dest = os.path.join(self.tools_root, tool_id)
|
dest = os.path.join(self.tools_root, tool_id)
|
||||||
git_dir = os.path.join(dest, ".git")
|
git_dir = os.path.join(dest, ".git")
|
||||||
|
|
||||||
|
# Step-down: each branch resolves one situation, runs the
|
||||||
|
# appropriate clone/pull+yarn sequence, and assigns msg.
|
||||||
if os.path.isdir(git_dir):
|
if os.path.isdir(git_dir):
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
|
|
@ -653,12 +644,8 @@ class InstallerManager:
|
||||||
"git pull / yarn install failed for %s (%s); re-cloning",
|
"git pull / yarn install failed for %s (%s); re-cloning",
|
||||||
tool_id, exc,
|
tool_id, exc,
|
||||||
)
|
)
|
||||||
backup = dest + ".bak." + str(int(time.time()))
|
backup = self._backup_existing(dest)
|
||||||
shutil.move(dest, backup)
|
self._clone_fresh(pkg, dest)
|
||||||
os.makedirs(dest, exist_ok=True)
|
|
||||||
subprocess.run(
|
|
||||||
["git", "clone", pkg, dest], check=True, timeout=600,
|
|
||||||
)
|
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["yarn", "install"], cwd=dest, check=True, timeout=300,
|
["yarn", "install"], cwd=dest, check=True, timeout=300,
|
||||||
)
|
)
|
||||||
|
|
@ -667,12 +654,8 @@ class InstallerManager:
|
||||||
f"old copy at {backup}): {dest}"
|
f"old copy at {backup}): {dest}"
|
||||||
)
|
)
|
||||||
elif os.path.exists(dest):
|
elif os.path.exists(dest):
|
||||||
backup = dest + ".bak." + str(int(time.time()))
|
backup = self._backup_existing(dest)
|
||||||
shutil.move(dest, backup)
|
self._clone_fresh(pkg, dest)
|
||||||
os.makedirs(dest, exist_ok=True)
|
|
||||||
subprocess.run(
|
|
||||||
["git", "clone", pkg, dest], check=True, timeout=600,
|
|
||||||
)
|
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["yarn", "install"], cwd=dest, check=True, timeout=300,
|
["yarn", "install"], cwd=dest, check=True, timeout=300,
|
||||||
)
|
)
|
||||||
|
|
@ -681,10 +664,7 @@ class InstallerManager:
|
||||||
f"up at {backup}): {dest}"
|
f"up at {backup}): {dest}"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
os.makedirs(dest, exist_ok=True)
|
self._clone_fresh(pkg, dest)
|
||||||
subprocess.run(
|
|
||||||
["git", "clone", pkg, dest], check=True, timeout=600,
|
|
||||||
)
|
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
["yarn", "install"], cwd=dest, check=True, timeout=300,
|
["yarn", "install"], cwd=dest, check=True, timeout=300,
|
||||||
)
|
)
|
||||||
|
|
@ -694,6 +674,27 @@ class InstallerManager:
|
||||||
self._run_post_install(tool_id, post_install)
|
self._run_post_install(tool_id, post_install)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
# ── git install helpers ────────────────────────────────────────
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _backup_existing(dest: str) -> str:
|
||||||
|
"""Move *dest* aside to ``<dest>.bak.<unix_ts>`` and return the backup path.
|
||||||
|
|
||||||
|
Caller guarantees *dest* exists.
|
||||||
|
"""
|
||||||
|
backup = f"{dest}.bak.{int(time.time())}"
|
||||||
|
shutil.move(dest, backup)
|
||||||
|
return backup
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _clone_fresh(pkg: str, dest: str) -> None:
|
||||||
|
"""Create *dest* (empty) and run ``git clone pkg dest`` into it."""
|
||||||
|
os.makedirs(dest, exist_ok=True)
|
||||||
|
subprocess.run(
|
||||||
|
["git", "clone", pkg, dest],
|
||||||
|
check=True, timeout=600,
|
||||||
|
)
|
||||||
|
|
||||||
def install_script(
|
def install_script(
|
||||||
self,
|
self,
|
||||||
cmd: str,
|
cmd: str,
|
||||||
|
|
|
||||||
|
|
@ -360,35 +360,35 @@ if _HAS_QT:
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
html = (
|
# Dispatch table for the bubble-style key — replaces a
|
||||||
"<body style='background:#121212; color:#e0e0e0;"
|
# 2-level nested ternary on (is_user, is_error).
|
||||||
" font-family:sans-serif;'>"
|
_identity_color = {"user": "#e0e0e0", "error": "#e74c3c", "assistant": "#d35400"}
|
||||||
)
|
_identity_label = {
|
||||||
for msg in self.chat_messages:
|
"user": "<b>You</b>",
|
||||||
|
# assistant/error labels render identity inline; filled below.
|
||||||
|
}
|
||||||
|
|
||||||
|
def _style_key(msg: dict) -> str:
|
||||||
|
if msg["is_user"]:
|
||||||
|
return "user"
|
||||||
|
return "error" if "?" in msg["identity"] else "assistant"
|
||||||
|
|
||||||
|
def _render_bubble(msg: dict) -> str:
|
||||||
|
style_key = _style_key(msg)
|
||||||
|
s = bubble_styles[style_key]
|
||||||
ts = msg["timestamp"]
|
ts = msg["timestamp"]
|
||||||
payload = (
|
payload = (
|
||||||
msg["payload"]
|
msg["payload"]
|
||||||
.replace("<", "<").replace(">", ">")
|
.replace("<", "<").replace(">", ">")
|
||||||
.replace("\n", "<br/>")
|
.replace("\n", "<br/>")
|
||||||
)
|
)
|
||||||
is_error = "?" in msg["identity"]
|
if style_key == "user":
|
||||||
style_key = (
|
label = _identity_label["user"]
|
||||||
"user" if msg["is_user"]
|
else:
|
||||||
else ("error" if is_error else "assistant")
|
color = _identity_color[style_key]
|
||||||
)
|
label = f"<b style='color:{color}'>{msg['identity']}</b>"
|
||||||
s = bubble_styles[style_key]
|
border_css = f"border: {s['border']};" if "border" in s else ""
|
||||||
label_color = (
|
return (
|
||||||
"#e0e0e0" if msg["is_user"]
|
|
||||||
else ("#e74c3c" if is_error else "#d35400")
|
|
||||||
)
|
|
||||||
label = (
|
|
||||||
"<b>You</b>" if msg["is_user"]
|
|
||||||
else f"<b style='color:{label_color}'>{msg['identity']}</b>"
|
|
||||||
)
|
|
||||||
border_css = (
|
|
||||||
f"border: {s['border']};" if "border" in s else ""
|
|
||||||
)
|
|
||||||
html += (
|
|
||||||
f"<div style='margin-bottom:15px; text-align:{s['align']}';>"
|
f"<div style='margin-bottom:15px; text-align:{s['align']}';>"
|
||||||
f"<span style='background:{s['bg']}; {border_css} color:#e0e0e0; "
|
f"<span style='background:{s['bg']}; {border_css} color:#e0e0e0; "
|
||||||
f"padding:10px 14px; border-radius:{s['radius']}; "
|
f"padding:10px 14px; border-radius:{s['radius']}; "
|
||||||
|
|
@ -400,6 +400,12 @@ if _HAS_QT:
|
||||||
f"</span></div>"
|
f"</span></div>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
html = (
|
||||||
|
"<body style='background:#121212; color:#e0e0e0;"
|
||||||
|
" font-family:sans-serif;'>"
|
||||||
|
)
|
||||||
|
html += "".join(_render_bubble(m) for m in self.chat_messages)
|
||||||
|
|
||||||
if self.is_thinking:
|
if self.is_thinking:
|
||||||
model = self.cbo_chat_model.currentText() or "Model"
|
model = self.cbo_chat_model.currentText() or "Model"
|
||||||
html += (
|
html += (
|
||||||
|
|
@ -638,11 +644,9 @@ if _HAS_QT:
|
||||||
"Absorb and layer the following skill instructions:\n\n"
|
"Absorb and layer the following skill instructions:\n\n"
|
||||||
+ "\n\n".join(skill_directives)
|
+ "\n\n".join(skill_directives)
|
||||||
)
|
)
|
||||||
system_content = (
|
# Flat join beats the prior 3-way nested ternary; empty parts
|
||||||
parts[0] + "\n\n" + parts[1]
|
# naturally produce an empty string.
|
||||||
if len(parts) == 2
|
system_content = "\n\n".join(parts)
|
||||||
else (parts[0] if parts else "")
|
|
||||||
)
|
|
||||||
|
|
||||||
self.chat_history_data.append(
|
self.chat_history_data.append(
|
||||||
{"role": "user", "content": full_prompt}
|
{"role": "user", "content": full_prompt}
|
||||||
|
|
@ -650,9 +654,7 @@ if _HAS_QT:
|
||||||
filtered = [
|
filtered = [
|
||||||
m for m in self.chat_history_data if m.get("role") != "system"
|
m for m in self.chat_history_data if m.get("role") != "system"
|
||||||
]
|
]
|
||||||
result = []
|
result = [{"role": "system", "content": system_content}] if system_content else []
|
||||||
if system_content:
|
|
||||||
result.append({"role": "system", "content": system_content})
|
|
||||||
result.extend(filtered)
|
result.extend(filtered)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
**Pass scope**: CRITICAL (C-01, C-02, C-03, C-04, C-06, C-07) + all 24 HIGH + all 42 MEDIUM + all 20 LOW
|
**Pass scope**: CRITICAL (C-01, C-02, C-03, C-04, C-06, C-07) + all 24 HIGH + all 42 MEDIUM + all 20 LOW
|
||||||
**Pass status**: 91 of 93 findings addressed in code; 2 explicitly skipped per user instruction
|
**Pass status**: 91 of 93 findings addressed in code; 2 explicitly skipped per user instruction
|
||||||
**Double-check pass**: 19/19 functional spot-checks pass; 2 latent bugs found and fixed (see "Bugs caught during double-check" below)
|
**Double-check pass**: 19/19 functional spot-checks pass; 2 latent bugs found and fixed (see "Bugs caught during double-check" below)
|
||||||
**v3.2 note**: the 10-layer taxonomy migration preserved every curl|sh
|
**v3.2 note**: the 10-layer taxonomy migration carries every curl|sh
|
||||||
site below verbatim — the restored installer cmds (ollama, llamafile,
|
site below verbatim — the canonical installer cmds (ollama, llamafile,
|
||||||
qdrant, grafana_alloy, meilisearch, fabric) are byte-identical to the
|
qdrant, grafana_alloy, meilisearch, fabric) are byte-identical to the
|
||||||
pre-migration registry, so the C-05 policy state is unchanged.
|
pre-migration registry, so the C-05 policy state is unchanged.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue