v1.5.1 — Fix chat system, add system tray icon, agent mode improvements
- Fix Hal chat: add Chat/Agent mode toggle so users can switch between direct LLM streaming (Chat) and tool-using Agent mode - Fix Agent system: graceful degradation when model can't follow structured THOUGHT/ACTION/PARAMS format (falls back to direct answer after 2 parse failures instead of looping 20 times) - Fix frozen build: remove llama_cpp from PyInstaller excludes list so LLM works in compiled exe - Add system tray icon: autarch.ico (from icon.svg) used for exe icons, installer shortcuts, and runtime tray icon - Update tray.py to load .ico file with fallback to programmatic generation - Add inline critical CSS for FOUC prevention - Bump version to 1.5.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
13cdc5657e
commit
67b7edc696
179
DEVLOG.md
179
DEVLOG.md
@ -5373,3 +5373,182 @@ Added local network discovery so Archon can auto-find AUTARCH servers without ma
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Session 16 — 2026-03-01: Threat Monitor Enhancement, Hal Agent Mode, Windows Defense, LLM Trainer
|
||||||
|
|
||||||
|
### Phase 4.17 — Threat Monitor Enhancement (7-tab Threat Monitor)
|
||||||
|
|
||||||
|
Expanded the Threat Monitor from 4 tabs to 7, adding Network Intel, Packet Capture, and DDoS Mitigation capabilities.
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `modules/defender_monitor.py` — Added ~15 new methods + singleton `get_threat_monitor()`
|
||||||
|
- `web/routes/defense.py` — Added ~25 new routes under `/defense/monitor/`
|
||||||
|
- `web/templates/defense_monitor.html` — 3 new tabs (7 total), drill-down popups
|
||||||
|
|
||||||
|
**New ThreatMonitor methods:**
|
||||||
|
- `get_bandwidth()` — bytes in/out per interface + deltas (PowerShell / `/proc/net/dev`)
|
||||||
|
- `check_arp_spoofing()` — multiple MACs per IP detection (`arp -a` / `ip neigh show`)
|
||||||
|
- `check_new_listening_ports()` — alert on new listeners since baseline
|
||||||
|
- `geoip_lookup(ip)` — country/ISP/ASN via ipwho.is API
|
||||||
|
- `get_connections_with_geoip()` — connection table enriched with geo data
|
||||||
|
- `get_connection_rate()` — connections/sec trending
|
||||||
|
- `detect_ddos()` — SYN flood / connection flood / bandwidth spike detection
|
||||||
|
- `get_top_talkers(limit)` — top IPs by connection count
|
||||||
|
- `apply_rate_limit(ip, rate)` / `remove_rate_limit(ip)` — per-IP rate limiting (netsh / iptables)
|
||||||
|
- `get_syn_protection_status()` / `enable_syn_protection()` — SYN cookies
|
||||||
|
- `get_ddos_config()` / `save_ddos_config()` — auto-mitigation config (data/ddos_config.json)
|
||||||
|
- `auto_mitigate()` — auto-block offenders if thresholds exceeded
|
||||||
|
- `get_mitigation_history()` / `log_mitigation()` — action log (data/mitigation_log.json)
|
||||||
|
|
||||||
|
**New routes (under `/defense/monitor/`):**
|
||||||
|
- Monitoring: `bandwidth`, `arp-check`, `new-ports`, `geoip`, `connections-geo`, `connection-rate`
|
||||||
|
- Packet Capture: `capture/interfaces`, `capture/start`, `capture/stop`, `capture/stats`, `capture/stream` (SSE), `capture/protocols`, `capture/conversations`
|
||||||
|
- DDoS: `ddos/detect`, `ddos/top-talkers`, `ddos/rate-limit`, `ddos/rate-limit/remove`, `ddos/syn-status`, `ddos/syn-enable`, `ddos/syn-disable`, `ddos/config` (GET/POST), `ddos/auto-mitigate`, `ddos/history`, `ddos/history/clear`
|
||||||
|
|
||||||
|
**7 tabs in defense_monitor.html:**
|
||||||
|
1. **Live Monitor** — enhanced with bandwidth cards, ARP/port/DDoS counters, drill-down popups
|
||||||
|
2. **Connections** — existing, with clickable rows for connection details
|
||||||
|
3. **Network Intel** — bandwidth table, ARP spoof check, listening port monitor, GeoIP lookup, connections+GeoIP
|
||||||
|
4. **Threats** — existing threat list with drill-down
|
||||||
|
5. **Packet Capture** — interface selector, BPF filter, duration, start/stop, live packet SSE stream, protocol distribution, top conversations
|
||||||
|
6. **DDoS Mitigation** — detection status, top talkers, SYN protection toggle, rate limiting per IP, auto-mitigation config, mitigation history
|
||||||
|
7. **Counter-Attack** — existing
|
||||||
|
|
||||||
|
**Drill-down popups (`.tmon-overlay` + `.tmon-popup`):**
|
||||||
|
- Click any stat in Live Monitor → modal popup with detailed data table
|
||||||
|
- Connections popup with clickable rows → individual connection detail card
|
||||||
|
- CSS added: `.tmon-overlay`, `.tmon-popup`, `.tmon-popup-header`, `.tmon-popup-body`, `.tmon-stat-clickable`, `.tmon-detail-card`, `.tmon-row-clickable`, `.tmon-back-btn`
|
||||||
|
|
||||||
|
### Phase 4.18 — Hal Agent Mode + Module Factory
|
||||||
|
|
||||||
|
Wired Hal chat to the Agent system so it can create new AUTARCH modules on demand.
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `core/tools.py` — added `create_module` tool to ToolRegistry
|
||||||
|
- `web/routes/chat.py` — rewritten to use Agent system with system prompt; agent-mode SSE streaming
|
||||||
|
- `data/hal_system_prompt.txt` (NEW) — Hal's codebase knowledge (~2000 tokens)
|
||||||
|
|
||||||
|
**`create_module` tool:**
|
||||||
|
- Validates category (defense/offense/counter/analyze/osint/simulate)
|
||||||
|
- Validates code contains required module attributes (NAME, DESCRIPTION, VERSION, CATEGORY, def run())
|
||||||
|
- Prevents overwriting existing modules
|
||||||
|
- Writes to `modules/{name}.py`
|
||||||
|
- Attempts `importlib.util.spec_from_file_location` to verify valid Python
|
||||||
|
- If import fails, deletes the file and returns the error
|
||||||
|
|
||||||
|
**Chat route rewrite:**
|
||||||
|
- Loads system prompt from `data/hal_system_prompt.txt`
|
||||||
|
- Detects action requests → Agent mode vs simple chat
|
||||||
|
- Agent mode: creates `Agent(llm, tools)`, runs in background thread, streams steps via SSE
|
||||||
|
- SSE events: `thought`, `action`, `result`, `token`, `done`, `error`
|
||||||
|
|
||||||
|
### Phase 4.19 — Windows Defense Sub-Page
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
- `modules/defender_windows.py` — Windows security module with firewall, UAC, Defender AV, services, SSH, NTFS, event logs
|
||||||
|
- `web/templates/defense_windows.html` — multi-tab Windows defense UI
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `web/routes/defense.py` — added `defense.windows_index` route + Windows-specific API routes
|
||||||
|
- `web/templates/base.html` — added Linux/Windows/Threat Monitor sub-items under Defense sidebar
|
||||||
|
|
||||||
|
### Phase 4.20 — LLM Trainer
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
- `modules/llm_trainer.py` — LLM fine-tuning module (dataset management, training config, adapter listing)
|
||||||
|
- `web/routes/llm_trainer.py` — Flask blueprint for LLM Trainer page
|
||||||
|
- `web/templates/llm_trainer.html` — LLM Trainer UI
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Dataset management (create, list, delete JSONL datasets)
|
||||||
|
- Training configuration (model, epochs, learning rate, batch size)
|
||||||
|
- Adapter listing (LoRA/QLoRA adapters)
|
||||||
|
- Training status monitoring
|
||||||
|
|
||||||
|
### Refresh Modules Button
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `web/templates/base.html` — added "Refresh Modules" button in sidebar
|
||||||
|
- `web/static/js/app.js` — `reloadModules()` function POSTs to `/settings/reload-modules`
|
||||||
|
- `web/routes/settings.py` — `POST /settings/reload-modules` route calls `MenuSystem.reload_modules()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session 17 — 2026-03-02: System Tray, Dual-Exe Build, Installer Scripts, v1.5 Release
|
||||||
|
|
||||||
|
### Phase 4.21 — System Tray Icon
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
- `core/tray.py` — `TrayManager` class using pystray + PIL
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `autarch.py` — added `--no-tray` flag, tray integration in `--web` mode
|
||||||
|
|
||||||
|
**TrayManager features:**
|
||||||
|
- Auto-generates dark circle icon with cyan "A" using PIL
|
||||||
|
- Menu: status line, Start, Stop, Restart, Open Dashboard, Exit
|
||||||
|
- Dynamic menu state (Start disabled when running, Stop/Restart disabled when stopped)
|
||||||
|
- Uses `werkzeug.serving.make_server` for threaded Flask in background
|
||||||
|
- SSL context passthrough for HTTPS
|
||||||
|
- `TRAY_AVAILABLE` flag for graceful fallback on systems without pystray
|
||||||
|
|
||||||
|
### Phase 4.22 — Dual Executable Build + Frozen Path Support
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
- `autarch_web.py` — Windowless web launcher entry point (Win32GUI, no console window)
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `core/paths.py` — Frozen build support with dual-directory pattern
|
||||||
|
- `core/menu.py` — Module loading scans both bundled and user module directories
|
||||||
|
- `web/app.py` — Template/static paths resolve correctly in frozen (PyInstaller) builds
|
||||||
|
|
||||||
|
**Frozen build architecture:**
|
||||||
|
- `_FROZEN = getattr(sys, 'frozen', False)` detection
|
||||||
|
- `_BUNDLE_DIR` = `Path(sys._MEIPASS)` when frozen (read-only assets)
|
||||||
|
- `_APP_DIR` = `Path(sys.executable).parent` when frozen (writable data)
|
||||||
|
- New: `is_frozen()`, `get_bundle_dir()`, `get_user_modules_dir()`
|
||||||
|
- `get_config_path()` copies bundled config to writable location on first run
|
||||||
|
- Module loading: scans both `get_modules_dir()` (bundle) and `get_user_modules_dir()` (user), user overrides bundled
|
||||||
|
|
||||||
|
### Phase 4.23 — Installer Scripts
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
- `installer.iss` — Inno Setup script (lzma2, no solid compression for large files)
|
||||||
|
- `installer.nsi` — NSIS script with MUI2, Start Menu, desktop shortcut, uninstaller
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `autarch_public.spec` — Rewritten for dual-exe build with MERGE/COLLECT, existence-filtered data files
|
||||||
|
- `setup_msi.py` — Dual executables, LocalAppData install, model inclusion
|
||||||
|
|
||||||
|
**PyInstaller spec details:**
|
||||||
|
- Dual Analysis: `a_cli` (autarch.py, console=True) + `a_web` (autarch_web.py, console=False)
|
||||||
|
- `MERGE()` for shared library deduplication
|
||||||
|
- Single `COLLECT` combining both executables
|
||||||
|
- Existence filter: `added_files = [(str(src), dst) for src, dst in _candidate_files if src.exists()]`
|
||||||
|
|
||||||
|
**Inno Setup details:**
|
||||||
|
- GGUF model stored with `Flags: nocompression` to avoid OOM (3.9GB, barely compressible)
|
||||||
|
- `SolidCompression=no` prevents Inno from loading entire archive into memory
|
||||||
|
- Model excluded from main recursive glob with `Excludes: "_internal\models\Hal_v2.gguf"`
|
||||||
|
- GitHub release version excludes model (34 MB vs 3.9 GB)
|
||||||
|
|
||||||
|
### Phase 4.24 — WebUI FOUC Fix
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `web/templates/base.html` — added inline critical CSS in `<head>`
|
||||||
|
|
||||||
|
**Fix:** Inlined dark theme colors, sidebar layout, and flex container styles directly in `<style>` tag before the external stylesheet `<link>`. Prevents flash of unstyled content (white background, unstyled sidebar) when the external CSS is delayed by self-signed cert negotiation or slow loading.
|
||||||
|
|
||||||
|
### v1.5 Release
|
||||||
|
|
||||||
|
**Release:** https://github.com/DigijEth/autarch/releases/tag/v1.5
|
||||||
|
|
||||||
|
**Assets:**
|
||||||
|
- `AUTARCH_Setup.exe` (34 MB) — Inno Setup installer, installs to `%LocalAppData%\AUTARCH`
|
||||||
|
- `AUTARCH_v1.5_Portable.zip` (39 MB) — Portable build with `autarch.exe` + `autarch_web.exe`
|
||||||
|
|
||||||
|
**Note:** Hal AI model (`Hal_v2.gguf`, 3.9 GB) excluded from both downloads due to GitHub's 2 GB per-asset limit.
|
||||||
|
|
||||||
|
**All 27+ pages tested** — inline CSS + external stylesheet present, layout/sidebar/content structure verified on every route.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|||||||
BIN
autarch.ico
Normal file
BIN
autarch.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
@ -29,6 +29,9 @@ _candidate_files = [
|
|||||||
# LLM model
|
# LLM model
|
||||||
(SRC / 'models' / 'Hal_v2.gguf', 'models'),
|
(SRC / 'models' / 'Hal_v2.gguf', 'models'),
|
||||||
|
|
||||||
|
# Icon
|
||||||
|
(SRC / 'autarch.ico', '.'),
|
||||||
|
|
||||||
# Root-level config and docs
|
# Root-level config and docs
|
||||||
(SRC / 'autarch_settings.conf', '.'),
|
(SRC / 'autarch_settings.conf', '.'),
|
||||||
(SRC / 'user_manual.md', '.'),
|
(SRC / 'user_manual.md', '.'),
|
||||||
@ -100,7 +103,7 @@ hidden_imports = [
|
|||||||
|
|
||||||
excludes = [
|
excludes = [
|
||||||
# Exclude heavy optional deps not needed at runtime
|
# Exclude heavy optional deps not needed at runtime
|
||||||
'torch', 'transformers', 'llama_cpp', 'llama_cpp_python', 'anthropic',
|
'torch', 'transformers',
|
||||||
'tkinter', 'matplotlib', 'numpy',
|
'tkinter', 'matplotlib', 'numpy',
|
||||||
# CUDA / quantization libraries
|
# CUDA / quantization libraries
|
||||||
'bitsandbytes',
|
'bitsandbytes',
|
||||||
@ -171,7 +174,7 @@ exe_cli = EXE(
|
|||||||
target_arch=None,
|
target_arch=None,
|
||||||
codesign_identity=None,
|
codesign_identity=None,
|
||||||
entitlements_file=None,
|
entitlements_file=None,
|
||||||
icon=None,
|
icon=str(SRC / 'autarch.ico'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── Web executable (NO console window — tray icon only) ─────────────────────
|
# ── Web executable (NO console window — tray icon only) ─────────────────────
|
||||||
@ -192,7 +195,7 @@ exe_web = EXE(
|
|||||||
target_arch=None,
|
target_arch=None,
|
||||||
codesign_identity=None,
|
codesign_identity=None,
|
||||||
entitlements_file=None,
|
entitlements_file=None,
|
||||||
icon=None,
|
icon=str(SRC / 'autarch.ico'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── Collect everything into one directory ────────────────────────────────────
|
# ── Collect everything into one directory ────────────────────────────────────
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
[llama]
|
[llama]
|
||||||
model_path = C:\she\autarch\models\Hal_v2.gguf
|
model_path = C:\she\autarch\models\darkHal.gguf
|
||||||
n_ctx = 2048
|
n_ctx = 2048
|
||||||
n_threads = 4
|
n_threads = 4
|
||||||
n_gpu_layers = 0
|
n_gpu_layers = -1
|
||||||
temperature = 0.7
|
temperature = 0.7
|
||||||
top_p = 0.9
|
top_p = 0.9
|
||||||
top_k = 40
|
top_k = 40
|
||||||
repeat_penalty = 1.1
|
repeat_penalty = 1.1
|
||||||
max_tokens = 1024
|
max_tokens = 1024
|
||||||
seed = -1
|
seed = -1
|
||||||
n_batch = 256
|
n_batch = 512
|
||||||
rope_scaling_type = 0
|
rope_scaling_type = 0
|
||||||
mirostat_mode = 0
|
mirostat_mode = 0
|
||||||
mirostat_tau = 5.0
|
mirostat_tau = 5.0
|
||||||
mirostat_eta = 0.1
|
mirostat_eta = 0.1
|
||||||
flash_attn = false
|
flash_attn = false
|
||||||
gpu_backend = cpu
|
gpu_backend = vulkan
|
||||||
|
|
||||||
[autarch]
|
[autarch]
|
||||||
first_run = false
|
first_run = false
|
||||||
|
|||||||
@ -249,6 +249,7 @@ PARAMS: {"question": "Your question"}
|
|||||||
self.conversation.append({"role": "user", "content": f"Task: {task}"})
|
self.conversation.append({"role": "user", "content": f"Task: {task}"})
|
||||||
|
|
||||||
step_count = 0
|
step_count = 0
|
||||||
|
parse_failures = 0 # Track consecutive format failures
|
||||||
|
|
||||||
while step_count < self.max_steps:
|
while step_count < self.max_steps:
|
||||||
step_count += 1
|
step_count += 1
|
||||||
@ -275,10 +276,34 @@ PARAMS: {"question": "Your question"}
|
|||||||
# Parse response
|
# Parse response
|
||||||
try:
|
try:
|
||||||
thought, action, params = self._parse_response(response)
|
thought, action, params = self._parse_response(response)
|
||||||
|
parse_failures = 0 # Reset on success
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
parse_failures += 1
|
||||||
self._log(f"Failed to parse response: {e}", "error")
|
self._log(f"Failed to parse response: {e}", "error")
|
||||||
self._log(f"Raw response: {response[:200]}...", "warning")
|
self._log(f"Raw response: {response[:200]}...", "warning")
|
||||||
# Add error feedback and continue
|
|
||||||
|
# After 2 consecutive parse failures, the model can't follow
|
||||||
|
# the structured format — treat its response as a direct answer
|
||||||
|
if parse_failures >= 2:
|
||||||
|
# Clean up the raw response for display
|
||||||
|
answer = response.strip()
|
||||||
|
# Remove ChatML tokens if present
|
||||||
|
for tag in ['<|im_end|>', '<|im_start|>', '<|endoftext|>']:
|
||||||
|
answer = answer.split(tag)[0]
|
||||||
|
answer = answer.strip()
|
||||||
|
if not answer:
|
||||||
|
answer = "I could not process that request in agent mode. Try switching to Chat mode."
|
||||||
|
|
||||||
|
self._log("Model cannot follow structured format, returning direct answer", "warning")
|
||||||
|
step = AgentStep(thought="Direct response (model does not support agent format)", tool_name="task_complete", tool_args={"summary": answer})
|
||||||
|
step.tool_result = answer
|
||||||
|
self.steps.append(step)
|
||||||
|
if self.on_step:
|
||||||
|
self.on_step(step)
|
||||||
|
self._set_state(AgentState.COMPLETE)
|
||||||
|
return AgentResult(success=True, summary=answer, steps=self.steps)
|
||||||
|
|
||||||
|
# First failure — give one retry with format correction
|
||||||
self.conversation.append({
|
self.conversation.append({
|
||||||
"role": "assistant",
|
"role": "assistant",
|
||||||
"content": response
|
"content": response
|
||||||
|
|||||||
31
core/tray.py
31
core/tray.py
@ -9,6 +9,7 @@ Requires: pystray, Pillow
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pystray
|
import pystray
|
||||||
@ -18,27 +19,43 @@ except ImportError:
|
|||||||
TRAY_AVAILABLE = False
|
TRAY_AVAILABLE = False
|
||||||
|
|
||||||
|
|
||||||
|
def _get_icon_path():
|
||||||
|
"""Find the .ico file — works in both source and frozen (PyInstaller) builds."""
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
base = Path(sys._MEIPASS)
|
||||||
|
else:
|
||||||
|
base = Path(__file__).parent.parent
|
||||||
|
ico = base / 'autarch.ico'
|
||||||
|
if ico.exists():
|
||||||
|
return ico
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def create_icon_image(size=64):
|
def create_icon_image(size=64):
|
||||||
"""Create AUTARCH tray icon programmatically — dark circle with cyan 'A'."""
|
"""Load tray icon from .ico file, falling back to programmatic generation."""
|
||||||
|
ico_path = _get_icon_path()
|
||||||
|
if ico_path:
|
||||||
|
try:
|
||||||
|
img = Image.open(str(ico_path))
|
||||||
|
img = img.resize((size, size), Image.LANCZOS)
|
||||||
|
return img.convert('RGBA')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Fallback: generate programmatically
|
||||||
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
img = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
|
|
||||||
# Dark background circle with cyan border
|
|
||||||
draw.ellipse([1, 1, size - 2, size - 2], fill=(15, 15, 25, 255),
|
draw.ellipse([1, 1, size - 2, size - 2], fill=(15, 15, 25, 255),
|
||||||
outline=(0, 180, 255, 255), width=2)
|
outline=(0, 180, 255, 255), width=2)
|
||||||
|
|
||||||
# Letter "A" centered
|
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype("arial.ttf", int(size * 0.5))
|
font = ImageFont.truetype("arial.ttf", int(size * 0.5))
|
||||||
except OSError:
|
except OSError:
|
||||||
font = ImageFont.load_default()
|
font = ImageFont.load_default()
|
||||||
|
|
||||||
bbox = draw.textbbox((0, 0), "A", font=font)
|
bbox = draw.textbbox((0, 0), "A", font=font)
|
||||||
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
||||||
x = (size - tw) // 2
|
x = (size - tw) // 2
|
||||||
y = (size - th) // 2 - bbox[1]
|
y = (size - th) // 2 - bbox[1]
|
||||||
draw.text((x, y), "A", fill=(0, 200, 255, 255), font=font)
|
draw.text((x, y), "A", fill=(0, 200, 255, 255), font=font)
|
||||||
|
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2262,3 +2262,65 @@ Full Hash Toolkit added as a sub-page under Analyze (sidebar sub-item like Legen
|
|||||||
- **Android Protection Direct mode** — `apDirect()` was passing `HWDirect.adbShell()` result objects (dicts) into `raw` instead of extracting `.stdout` strings; Python `/parse` route then crashed calling `.strip()` on dicts. Fixed by extracting stdout before sending to server
|
- **Android Protection Direct mode** — `apDirect()` was passing `HWDirect.adbShell()` result objects (dicts) into `raw` instead of extracting `.stdout` strings; Python `/parse` route then crashed calling `.strip()` on dicts. Fixed by extracting stdout before sending to server
|
||||||
- **`_serial()` hardened** — now checks `request.form` fallback and wraps in `str()` before `.strip()`
|
- **`_serial()` hardened** — now checks `request.form` fallback and wraps in `str()` before `.strip()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session 16 — 2026-03-01: Threat Monitor, Hal Agent, Windows Defense, LLM Trainer
|
||||||
|
|
||||||
|
### What got done this session:
|
||||||
|
- **7-tab Threat Monitor** — expanded from 4 tabs to 7 with Network Intel, Packet Capture, DDoS Mitigation
|
||||||
|
- **Drill-down popups** — click any stat in Live Monitor for detailed modal views
|
||||||
|
- **Hal Agent Mode** — Chat bubble now uses Agent system with `create_module` tool; can create modules on demand
|
||||||
|
- **System prompt** — `data/hal_system_prompt.txt` teaches Hal the codebase
|
||||||
|
- **Windows Defense** — `modules/defender_windows.py` + `defense_windows.html` (firewall, UAC, Defender AV, services, SSH, NTFS, event logs)
|
||||||
|
- **LLM Trainer** — `modules/llm_trainer.py` + `web/routes/llm_trainer.py` + `llm_trainer.html` (dataset management, training, adapters)
|
||||||
|
- **Refresh Modules** — sidebar button for hot-reloading modules without server restart
|
||||||
|
|
||||||
|
### Todos from session 14 resolved:
|
||||||
|
- System Tray → deferred to session 17
|
||||||
|
- Beta Release → deferred to session 17
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session 17 — 2026-03-02: System Tray, Packaging, v1.5 Release
|
||||||
|
|
||||||
|
### What got done this session:
|
||||||
|
- **System tray** — `core/tray.py` with `TrayManager` (pystray + PIL): Start/Stop/Restart/Open Dashboard/Exit
|
||||||
|
- **Dual executables** — `autarch.exe` (CLI, console) + `autarch_web.exe` (Web, no console, tray icon)
|
||||||
|
- **PyInstaller frozen build fixes** — dual-directory pattern in `core/paths.py` (_BUNDLE_DIR vs _APP_DIR), module loading scans both bundled and user dirs
|
||||||
|
- **Installer scripts** — `installer.iss` (Inno Setup) + `installer.nsi` (NSIS)
|
||||||
|
- **Inno Setup OOM fix** — 3.9GB model stored uncompressed, `SolidCompression=no`
|
||||||
|
- **Inline critical CSS** — prevents white flash / FOUC on page load
|
||||||
|
- **All 27+ pages tested** — verified inline CSS, external stylesheet, layout structure
|
||||||
|
- **Version bumped to 1.5**
|
||||||
|
- **GitHub Release v1.5** — https://github.com/DigijEth/autarch/releases/tag/v1.5
|
||||||
|
- `AUTARCH_Setup.exe` (34 MB) — installer without model
|
||||||
|
- `AUTARCH_v1.5_Portable.zip` (39 MB) — portable without model
|
||||||
|
|
||||||
|
### SESSION SAVE — 2026-03-02 (end of session)
|
||||||
|
|
||||||
|
**Phase status:**
|
||||||
|
- Phases 0–4.24: DONE
|
||||||
|
- Phase 5 (Path portability): DONE (frozen build support complete)
|
||||||
|
- Phase 6 (Docker): NOT STARTED
|
||||||
|
|
||||||
|
**Key files created/modified this session:**
|
||||||
|
- `core/tray.py` (NEW) — TrayManager
|
||||||
|
- `autarch_web.py` (NEW) — Windowless web launcher
|
||||||
|
- `installer.iss` (NEW) — Inno Setup installer script
|
||||||
|
- `installer.nsi` (NEW) — NSIS installer script
|
||||||
|
- `core/paths.py` — Frozen build dual-directory pattern
|
||||||
|
- `core/menu.py` — Dual module directory scanning
|
||||||
|
- `web/app.py` — Frozen template/static path resolution
|
||||||
|
- `autarch.py` — --no-tray flag
|
||||||
|
- `autarch_public.spec` — Dual-exe MERGE/COLLECT
|
||||||
|
- `setup_msi.py` — Dual executables, v1.5
|
||||||
|
- `web/templates/base.html` — Inline critical CSS
|
||||||
|
|
||||||
|
**Todos from session 14 RESOLVED:**
|
||||||
|
- System Tray: DONE (core/tray.py)
|
||||||
|
- Beta Release: DONE (v1.5 on GitHub)
|
||||||
|
|
||||||
|
**Remaining work from master_plan.md:**
|
||||||
|
- Phase 6 (Docker): NOT STARTED
|
||||||
|
- Plan file (quizzical-toasting-mccarthy.md) — Threat Monitor + Hal Module Factory: DONE
|
||||||
|
|
||||||
|
|||||||
87
icon.svg
Normal file
87
icon.svg
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||||
|
<defs>
|
||||||
|
<!-- Neon cyan glow -->
|
||||||
|
<filter id="glow">
|
||||||
|
<feGaussianBlur stdDeviation="6" result="blur"/>
|
||||||
|
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
|
||||||
|
</filter>
|
||||||
|
<!-- Heavy outer glow -->
|
||||||
|
<filter id="glowHeavy">
|
||||||
|
<feGaussianBlur stdDeviation="12" result="blur1"/>
|
||||||
|
<feGaussianBlur stdDeviation="24" result="blur2"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="blur2"/>
|
||||||
|
<feMergeNode in="blur1"/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
<!-- Scanlines pattern -->
|
||||||
|
<pattern id="scanlines" width="4" height="4" patternUnits="userSpaceOnUse">
|
||||||
|
<rect width="4" height="2" fill="rgba(0,255,255,0.03)"/>
|
||||||
|
<rect y="2" width="4" height="2" fill="rgba(0,0,0,0.15)"/>
|
||||||
|
</pattern>
|
||||||
|
<!-- Grid pattern -->
|
||||||
|
<pattern id="grid" width="32" height="32" patternUnits="userSpaceOnUse">
|
||||||
|
<path d="M 32 0 L 0 0 0 32" fill="none" stroke="rgba(0,255,255,0.06)" stroke-width="0.5"/>
|
||||||
|
</pattern>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Dark background -->
|
||||||
|
<rect width="512" height="512" fill="#05080f"/>
|
||||||
|
<!-- Grid overlay -->
|
||||||
|
<rect width="512" height="512" fill="url(#grid)"/>
|
||||||
|
|
||||||
|
<!-- Faint radial ambiance -->
|
||||||
|
<circle cx="256" cy="256" r="240" fill="none" stroke="rgba(255,0,80,0.04)" stroke-width="180"/>
|
||||||
|
<circle cx="256" cy="256" r="180" fill="none" stroke="rgba(0,255,255,0.03)" stroke-width="120"/>
|
||||||
|
|
||||||
|
<!-- Glitch offset layers (red/blue chromatic aberration) -->
|
||||||
|
<g opacity="0.35">
|
||||||
|
<!-- Red offset -->
|
||||||
|
<circle cx="259" cy="254" r="200" fill="none" stroke="#ff0040" stroke-width="20"/>
|
||||||
|
<line x1="259" y1="54" x2="123" y2="428" stroke="#ff0040" stroke-width="20" stroke-linecap="round"/>
|
||||||
|
<line x1="259" y1="54" x2="395" y2="428" stroke="#ff0040" stroke-width="20" stroke-linecap="round"/>
|
||||||
|
<line x1="103" y1="318" x2="415" y2="318" stroke="#ff0040" stroke-width="20" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
<g opacity="0.35">
|
||||||
|
<!-- Blue offset -->
|
||||||
|
<circle cx="253" cy="258" r="200" fill="none" stroke="#00d4ff" stroke-width="20"/>
|
||||||
|
<line x1="253" y1="58" x2="117" y2="432" stroke="#00d4ff" stroke-width="20" stroke-linecap="round"/>
|
||||||
|
<line x1="253" y1="58" x2="389" y2="432" stroke="#00d4ff" stroke-width="20" stroke-linecap="round"/>
|
||||||
|
<line x1="97" y1="322" x2="409" y2="322" stroke="#00d4ff" stroke-width="20" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Main symbol with neon glow -->
|
||||||
|
<g filter="url(#glowHeavy)">
|
||||||
|
<circle cx="256" cy="256" r="200" fill="none" stroke="#00ffcc" stroke-width="18"/>
|
||||||
|
<line x1="256" y1="56" x2="120" y2="430" stroke="#00ffcc" stroke-width="18" stroke-linecap="round"/>
|
||||||
|
<line x1="256" y1="56" x2="392" y2="430" stroke="#00ffcc" stroke-width="18" stroke-linecap="round"/>
|
||||||
|
<line x1="100" y1="320" x2="412" y2="320" stroke="#00ffcc" stroke-width="18" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Bright core layer -->
|
||||||
|
<g filter="url(#glow)">
|
||||||
|
<circle cx="256" cy="256" r="200" fill="none" stroke="#ffffff" stroke-width="4" opacity="0.6"/>
|
||||||
|
<line x1="256" y1="56" x2="120" y2="430" stroke="#ffffff" stroke-width="4" opacity="0.6" stroke-linecap="round"/>
|
||||||
|
<line x1="256" y1="56" x2="392" y2="430" stroke="#ffffff" stroke-width="4" opacity="0.6" stroke-linecap="round"/>
|
||||||
|
<line x1="100" y1="320" x2="412" y2="320" stroke="#ffffff" stroke-width="4" opacity="0.6" stroke-linecap="round"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Glitch bars -->
|
||||||
|
<rect x="0" y="170" width="512" height="3" fill="#00ffcc" opacity="0.15"/>
|
||||||
|
<rect x="0" y="340" width="512" height="2" fill="#ff0040" opacity="0.12"/>
|
||||||
|
<rect x="0" y="405" width="512" height="1.5" fill="#00ffcc" opacity="0.1"/>
|
||||||
|
<rect x="180" y="168" width="90" height="6" fill="#05080f" opacity="0.8"/>
|
||||||
|
<rect x="300" y="338" width="60" height="5" fill="#05080f" opacity="0.8"/>
|
||||||
|
|
||||||
|
<!-- Scanlines overlay -->
|
||||||
|
<rect width="512" height="512" fill="url(#scanlines)"/>
|
||||||
|
|
||||||
|
<!-- Corner accents -->
|
||||||
|
<g stroke="#00ffcc" stroke-width="2" opacity="0.4">
|
||||||
|
<polyline points="10,40 10,10 40,10" fill="none"/>
|
||||||
|
<polyline points="472,10 502,10 502,40" fill="none"/>
|
||||||
|
<polyline points="10,472 10,502 40,502" fill="none"/>
|
||||||
|
<polyline points="472,502 502,502 502,472" fill="none"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
[Setup]
|
[Setup]
|
||||||
AppName=AUTARCH
|
AppName=AUTARCH
|
||||||
AppVersion=1.5
|
AppVersion=1.5.1
|
||||||
AppVerName=AUTARCH 1.5
|
AppVerName=AUTARCH 1.5.1
|
||||||
AppPublisher=darkHal Security Group
|
AppPublisher=darkHal Security Group
|
||||||
AppPublisherURL=https://github.com/darkhal
|
AppPublisherURL=https://github.com/darkhal
|
||||||
AppSupportURL=https://github.com/darkhal
|
AppSupportURL=https://github.com/darkhal
|
||||||
@ -32,8 +32,8 @@ DisableProgramGroupPage=yes
|
|||||||
WizardStyle=modern
|
WizardStyle=modern
|
||||||
SetupLogging=yes
|
SetupLogging=yes
|
||||||
|
|
||||||
; Uncomment and set path if you have a custom icon:
|
SetupIconFile=autarch.ico
|
||||||
; SetupIconFile=assets\autarch.ico
|
UninstallDisplayIcon={app}\autarch_web.exe
|
||||||
|
|
||||||
[Languages]
|
[Languages]
|
||||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
@ -49,12 +49,12 @@ Source: "dist\autarch\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
|
|||||||
|
|
||||||
[Icons]
|
[Icons]
|
||||||
; Start Menu
|
; Start Menu
|
||||||
Name: "{group}\AUTARCH Web Dashboard"; Filename: "{app}\autarch_web.exe"; Comment: "Launch AUTARCH Web Dashboard with system tray"
|
Name: "{group}\AUTARCH Web Dashboard"; Filename: "{app}\autarch_web.exe"; IconFilename: "{app}\autarch.ico"; Comment: "Launch AUTARCH Web Dashboard with system tray"
|
||||||
Name: "{group}\AUTARCH CLI"; Filename: "{app}\autarch.exe"; Comment: "AUTARCH command-line interface"
|
Name: "{group}\AUTARCH CLI"; Filename: "{app}\autarch.exe"; IconFilename: "{app}\autarch.ico"; Comment: "AUTARCH command-line interface"
|
||||||
Name: "{group}\Uninstall AUTARCH"; Filename: "{uninstallexe}"
|
Name: "{group}\Uninstall AUTARCH"; Filename: "{uninstallexe}"
|
||||||
|
|
||||||
; Desktop (optional)
|
; Desktop (optional)
|
||||||
Name: "{commondesktop}\AUTARCH Web"; Filename: "{app}\autarch_web.exe"; Tasks: desktopicon; Comment: "Launch AUTARCH Web Dashboard"
|
Name: "{commondesktop}\AUTARCH Web"; Filename: "{app}\autarch_web.exe"; IconFilename: "{app}\autarch.ico"; Tasks: desktopicon; Comment: "Launch AUTARCH Web Dashboard"
|
||||||
|
|
||||||
; Windows Startup (optional)
|
; Windows Startup (optional)
|
||||||
Name: "{userstartup}\AUTARCH Web"; Filename: "{app}\autarch_web.exe"; Tasks: startupicon
|
Name: "{userstartup}\AUTARCH Web"; Filename: "{app}\autarch_web.exe"; Tasks: startupicon
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
; ── App metadata ─────────────────────────────────────────────────────────────
|
; ── App metadata ─────────────────────────────────────────────────────────────
|
||||||
!define APPNAME "AUTARCH"
|
!define APPNAME "AUTARCH"
|
||||||
!define APPVERSION "1.5"
|
!define APPVERSION "1.5.1"
|
||||||
!define PUBLISHER "darkHal Security Group"
|
!define PUBLISHER "darkHal Security Group"
|
||||||
!define DESCRIPTION "Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking"
|
!define DESCRIPTION "Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking"
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,7 @@ build_exe_options = {
|
|||||||
'web.routes.targets', 'web.routes.encmodules',
|
'web.routes.targets', 'web.routes.encmodules',
|
||||||
'web.routes.llm_trainer',
|
'web.routes.llm_trainer',
|
||||||
],
|
],
|
||||||
'excludes': ['torch', 'transformers', 'llama_cpp', 'llama_cpp_python', 'anthropic',
|
'excludes': ['torch', 'transformers',
|
||||||
'tkinter', 'matplotlib', 'numpy',
|
'tkinter', 'matplotlib', 'numpy',
|
||||||
'bitsandbytes',
|
'bitsandbytes',
|
||||||
'huggingface_hub', 'safetensors', 'tokenizers',
|
'huggingface_hub', 'safetensors', 'tokenizers',
|
||||||
@ -74,7 +74,7 @@ bdist_msi_options = {
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='AUTARCH',
|
name='AUTARCH',
|
||||||
version='1.5.0',
|
version='1.5.1',
|
||||||
description='AUTARCH — Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking',
|
description='AUTARCH — Autonomous Tactical Agent for Reconnaissance, Counterintelligence, and Hacking',
|
||||||
author='darkHal Security Group & Setec Security Labs',
|
author='darkHal Security Group & Setec Security Labs',
|
||||||
options={
|
options={
|
||||||
|
|||||||
@ -45,14 +45,51 @@ def _ensure_model_loaded():
|
|||||||
@chat_bp.route('/chat', methods=['POST'])
|
@chat_bp.route('/chat', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def chat():
|
def chat():
|
||||||
"""Handle chat messages — uses Agent system for tool-using tasks,
|
"""Handle chat messages — direct chat or agent mode based on user toggle.
|
||||||
direct chat for simple questions. Streams response via SSE."""
|
Streams response via SSE."""
|
||||||
data = request.get_json(silent=True) or {}
|
data = request.get_json(silent=True) or {}
|
||||||
message = data.get('message', '').strip()
|
message = data.get('message', '').strip()
|
||||||
|
mode = data.get('mode', 'chat') # 'chat' (default) or 'agent'
|
||||||
if not message:
|
if not message:
|
||||||
return jsonify({'error': 'No message provided'})
|
return jsonify({'error': 'No message provided'})
|
||||||
|
|
||||||
# Always use agent mode so Hal can use tools including create_module
|
if mode == 'agent':
|
||||||
|
return _handle_agent_chat(message)
|
||||||
|
else:
|
||||||
|
return _handle_direct_chat(message)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_direct_chat(message):
|
||||||
|
"""Direct chat mode — streams tokens from the LLM without the Agent system."""
|
||||||
|
def generate():
|
||||||
|
from core.llm import get_llm, LLMError
|
||||||
|
|
||||||
|
llm = get_llm()
|
||||||
|
if not llm.is_loaded:
|
||||||
|
yield f"data: {json.dumps({'type': 'status', 'content': 'Loading model...'})}\n\n"
|
||||||
|
try:
|
||||||
|
llm.load_model(verbose=False)
|
||||||
|
except LLMError as e:
|
||||||
|
yield f"data: {json.dumps({'type': 'error', 'content': f'Failed to load model: {e}'})}\n\n"
|
||||||
|
yield f"data: {json.dumps({'done': True})}\n\n"
|
||||||
|
return
|
||||||
|
|
||||||
|
system_prompt = _get_system_prompt()
|
||||||
|
try:
|
||||||
|
token_gen = llm.chat(message, system_prompt=system_prompt, stream=True)
|
||||||
|
for token in token_gen:
|
||||||
|
yield f"data: {json.dumps({'token': token})}\n\n"
|
||||||
|
except LLMError as e:
|
||||||
|
yield f"data: {json.dumps({'type': 'error', 'content': str(e)})}\n\n"
|
||||||
|
|
||||||
|
yield f"data: {json.dumps({'done': True})}\n\n"
|
||||||
|
|
||||||
|
return Response(generate(), mimetype='text/event-stream',
|
||||||
|
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'})
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_agent_chat(message):
|
||||||
|
"""Agent mode — uses the Agent system with tools for complex tasks."""
|
||||||
run_id = str(uuid.uuid4())
|
run_id = str(uuid.uuid4())
|
||||||
stop_event = threading.Event()
|
stop_event = threading.Event()
|
||||||
steps = []
|
steps = []
|
||||||
@ -86,7 +123,6 @@ def chat():
|
|||||||
if step.tool_name and step.tool_name not in ('task_complete', 'ask_user'):
|
if step.tool_name and step.tool_name not in ('task_complete', 'ask_user'):
|
||||||
steps.append({'type': 'action', 'content': f"{step.tool_name}({json.dumps(step.tool_args or {})})"})
|
steps.append({'type': 'action', 'content': f"{step.tool_name}({json.dumps(step.tool_args or {})})"})
|
||||||
if step.tool_result:
|
if step.tool_result:
|
||||||
# Truncate long results for display
|
|
||||||
result = step.tool_result
|
result = step.tool_result
|
||||||
if len(result) > 800:
|
if len(result) > 800:
|
||||||
result = result[:800] + '...'
|
result = result[:800] + '...'
|
||||||
|
|||||||
@ -668,6 +668,50 @@ pre { background: var(--bg-primary); border: 1px solid var(--border); border-rad
|
|||||||
}
|
}
|
||||||
.hal-close:hover { color: var(--text, #e0e0e0); background: var(--bg-hover, #2a2a3e); }
|
.hal-close:hover { color: var(--text, #e0e0e0); background: var(--bg-hover, #2a2a3e); }
|
||||||
|
|
||||||
|
/* Hal mode toggle switch */
|
||||||
|
.hal-mode-switch {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.hal-mode-switch input { display: none; }
|
||||||
|
.hal-mode-slider {
|
||||||
|
width: 28px;
|
||||||
|
height: 14px;
|
||||||
|
background: var(--bg-input, #2a2d3e);
|
||||||
|
border-radius: 7px;
|
||||||
|
position: relative;
|
||||||
|
transition: background 0.2s;
|
||||||
|
border: 1px solid var(--border, #333650);
|
||||||
|
}
|
||||||
|
.hal-mode-slider::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background: var(--text-secondary, #888);
|
||||||
|
border-radius: 50%;
|
||||||
|
top: 1px;
|
||||||
|
left: 1px;
|
||||||
|
transition: transform 0.2s, background 0.2s;
|
||||||
|
}
|
||||||
|
.hal-mode-switch input:checked + .hal-mode-slider {
|
||||||
|
background: var(--accent, #6366f1);
|
||||||
|
border-color: var(--accent, #6366f1);
|
||||||
|
}
|
||||||
|
.hal-mode-switch input:checked + .hal-mode-slider::after {
|
||||||
|
transform: translateX(14px);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.hal-mode-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary, #888);
|
||||||
|
min-width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
.hal-messages {
|
.hal-messages {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|||||||
@ -2109,6 +2109,14 @@ async function hwFactoryFlash() {
|
|||||||
|
|
||||||
// ── Agent Hal Global Chat Panel ──────────────────────────────────────────────
|
// ── Agent Hal Global Chat Panel ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
var halAgentMode = false; // false = direct chat, true = agent mode
|
||||||
|
|
||||||
|
function halModeChanged(checkbox) {
|
||||||
|
halAgentMode = checkbox.checked;
|
||||||
|
var label = document.getElementById('hal-mode-label');
|
||||||
|
if (label) label.textContent = halAgentMode ? 'Agent' : 'Chat';
|
||||||
|
}
|
||||||
|
|
||||||
function halToggle() {
|
function halToggle() {
|
||||||
var p = document.getElementById('hal-panel');
|
var p = document.getElementById('hal-panel');
|
||||||
if (!p) return;
|
if (!p) return;
|
||||||
@ -2133,7 +2141,7 @@ function halSend() {
|
|||||||
fetch('/api/chat', {
|
fetch('/api/chat', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({message: msg})
|
body: JSON.stringify({message: msg, mode: halAgentMode ? 'agent' : 'chat'})
|
||||||
}).then(function(res) {
|
}).then(function(res) {
|
||||||
var reader = res.body.getReader();
|
var reader = res.body.getReader();
|
||||||
var dec = new TextDecoder();
|
var dec = new TextDecoder();
|
||||||
|
|||||||
@ -130,7 +130,12 @@
|
|||||||
{% if session.get('user') %}
|
{% if session.get('user') %}
|
||||||
<div id="hal-panel" class="hal-panel" style="display:none">
|
<div id="hal-panel" class="hal-panel" style="display:none">
|
||||||
<div class="hal-header">
|
<div class="hal-header">
|
||||||
<span>● Agent Hal</span>
|
<span>● Hal</span>
|
||||||
|
<label class="hal-mode-switch" title="Toggle Agent mode (tools) vs Direct chat">
|
||||||
|
<input type="checkbox" id="hal-mode-toggle" onchange="halModeChanged(this)">
|
||||||
|
<span class="hal-mode-slider"></span>
|
||||||
|
<span class="hal-mode-label" id="hal-mode-label">Chat</span>
|
||||||
|
</label>
|
||||||
<button onclick="halToggle()" class="hal-close" title="Close">✕</button>
|
<button onclick="halToggle()" class="hal-close" title="Close">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="hal-messages" class="hal-messages"></div>
|
<div id="hal-messages" class="hal-messages"></div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user