mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-10-26 17:41:58 -06:00
Merge pull request #117 from bunny-lab-io:codex/rename-agent-settings-and-adjust-id-format
It seems that workflows now function properly and capture screen data. If you switch between CURRENTUSER and SYSTEM for the screenshot node (this should not be possible since SYSTEM has no screen), it glitches the screen capture and requires the user restarts the currentuser agent. This is a scenario that should not happen, so it will be enforced later based on the role to ensure that it cannot be used if the incorrect context is chosen in future updates.
This commit is contained in:
10
AGENTS.md
10
AGENTS.md
@@ -96,12 +96,12 @@ All runtime logs live under `Logs/<ServiceName>` relative to the project root (`
|
||||
## New: Agent Launch Model, Tasks, and Logging
|
||||
- SYSTEM mode is launched via a wrapper to guarantee WorkingDirectory and capture stdout/stderr:
|
||||
- `Agent\\Borealis\\launch_service.ps1` is registered as the scheduled task action for the SYSTEM agent.
|
||||
- The wrapper runs `Agent\\Scripts\\pythonw.exe Agent\\Borealis\\agent.py --system-service --config svc` with `Set-Location` to `Agent\\Borealis` and redirects output to `%ProgramData%\\Borealis\\svc.out.log` and `svc.err.log`.
|
||||
- The wrapper runs `Agent\\Scripts\\pythonw.exe Agent\\Borealis\\agent.py --system-service --config SYSTEM` with `Set-Location` to `Agent\\Borealis` and redirects output to `%ProgramData%\\Borealis\\svc.out.log` and `svc.err.log`.
|
||||
- This avoids 0x1/0x2 Task Scheduler errors on hosts where WorkingDirectory is ignored.
|
||||
- UserHelper (interactive) is still a direct task action to `pythonw.exe "Agent\\Borealis\\agent.py" --config user`.
|
||||
- UserHelper (interactive) is still a direct task action to `pythonw.exe "Agent\\Borealis\\agent.py" --config CURRENTUSER`.
|
||||
- Config files and inheritance:
|
||||
- Base config now lives at `<ProjectRoot>\\Agent\\Borealis\\Settings\\agent_settings.json`.
|
||||
- On first run per-suffix, the agent seeds: `Agent\\Borealis\\Settings\\agent_settings_svc.json` (SYSTEM) and `Agent\\Borealis\\Settings\\agent_settings_user.json` (interactive) from the base when present.
|
||||
- On first run per-suffix, the agent seeds: `Agent\\Borealis\\Settings\\agent_settings_SYSTEM.json` (SYSTEM) and `Agent\\Borealis\\Settings\\agent_settings_CURRENTUSER.json` (interactive) from the base when present.
|
||||
- Server URL is stored in `<ProjectRoot>\\Agent\\Borealis\\Settings\\server_url.txt`. The deployment script prompts for it on install/repair; press Enter to accept the default `http://localhost:5000`.
|
||||
- Logging:
|
||||
- Early bootstrap log: `<ProjectRoot>\\Logs\\Agent\\bootstrap.log` (helps verify launch + mode).
|
||||
@@ -115,8 +115,8 @@ All runtime logs live under `Logs/<ServiceName>` relative to the project root (`
|
||||
- Dev UI separately (if needed): `cd Server\\web-interface && npm run dev`.
|
||||
- Launch/repair agent (elevated PowerShell): `.\\Borealis.ps1 -Agent -AgentAction install`.
|
||||
- Manual short-run agent checks (non-blocking):
|
||||
- `Start-Process .\\Agent\\Scripts\\pythonw.exe -ArgumentList '".\\Agent\\Borealis\\agent.py" --system-service --config svc'`
|
||||
- Verify logs under `Logs\\Agent` and presence of `Agent\\Borealis\\Settings\\agent_settings_svc.json` and `Agent\\Borealis\\Settings\\server_url.txt`.
|
||||
- `Start-Process .\\Agent\\Scripts\\pythonw.exe -ArgumentList '".\\Agent\\Borealis\\agent.py" --system-service --config SYSTEM'`
|
||||
- Verify logs under `Logs\\Agent` and presence of `Agent\\Borealis\\Settings\\agent_settings_SYSTEM.json` and `Agent\\Borealis\\Settings\\server_url.txt`.
|
||||
|
||||
## Troubleshooting Checklist
|
||||
- Agent task “Ready” with 0x1: ensure the SYSTEM task uses `launch_service.ps1` and that WorkingDirectory is `Agent\\Borealis`.
|
||||
|
||||
@@ -715,7 +715,7 @@ function Ensure-AgentTasks {
|
||||
|
||||
# Optional user-session helper for interactive roles (tray, overlays)
|
||||
$helperName = 'Borealis Agent (UserHelper)'
|
||||
$usrArg = ('"{0}" --config user' -f $agentPy)
|
||||
$usrArg = ('"{0}" --config CURRENTUSER' -f $agentPy)
|
||||
$usrAction = New-ScheduledTaskAction -Execute $pyw -Argument $usrArg -WorkingDirectory (Split-Path $agentPy -Parent)
|
||||
$usrTrig = New-ScheduledTaskTrigger -AtLogOn
|
||||
$usrSet = New-ScheduledTaskSettingsSet -Hidden -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero)
|
||||
|
||||
@@ -215,7 +215,7 @@ cd "$ROOT_DIR"
|
||||
LOG_DIR="$(cd -- "$ROOT_DIR/../../Logs/Agent" && pwd 2>/dev/null || echo "$ROOT_DIR/../../Logs/Agent")"
|
||||
mkdir -p "$LOG_DIR"
|
||||
PY_BIN="${ROOT_DIR}/../bin/python3"
|
||||
exec "$PY_BIN" "$ROOT_DIR/agent.py" --system-service --config svc >>"$LOG_DIR/svc.out.log" 2>>"$LOG_DIR/svc.err.log"
|
||||
exec "$PY_BIN" "$ROOT_DIR/agent.py" --system-service --config SYSTEM >>"$LOG_DIR/svc.out.log" 2>>"$LOG_DIR/svc.err.log"
|
||||
SH
|
||||
chmod +x "${agentDest}/launch_service.sh"
|
||||
|
||||
@@ -229,7 +229,7 @@ ensure_agent_tasks() {
|
||||
# Register @reboot cron entries for system (root) and current user
|
||||
local agentDest="${SCRIPT_DIR}/Agent/Borealis"
|
||||
local sys_cmd="bash '${agentDest}/launch_service.sh'"
|
||||
local user_cmd="bash -lc 'cd "${agentDest}" && "${SCRIPT_DIR}/Agent/bin/python3" ./agent.py --config user'"
|
||||
local user_cmd="bash -lc 'cd "${agentDest}" && "${SCRIPT_DIR}/Agent/bin/python3" ./agent.py --config CURRENTUSER'"
|
||||
|
||||
# Root/system entry
|
||||
if need_sudo; then
|
||||
@@ -261,7 +261,7 @@ remove_agent() {
|
||||
log_agent "=== Removal start ===" Removal.log
|
||||
kill_agent_processes || true
|
||||
local sys_cmd="bash '${SCRIPT_DIR}/Agent/Borealis/launch_service.sh'"
|
||||
local user_cmd="bash -lc 'cd "${SCRIPT_DIR}/Agent/Borealis" && "${SCRIPT_DIR}/Agent/bin/python3" ./agent.py --config user'"
|
||||
local user_cmd="bash -lc 'cd "${SCRIPT_DIR}/Agent/Borealis" && "${SCRIPT_DIR}/Agent/bin/python3" ./agent.py --config CURRENTUSER'"
|
||||
remove_cron_entries "$sys_cmd" "$user_cmd" || true
|
||||
rm -rf "${SCRIPT_DIR}/Agent" || true
|
||||
log_agent "=== Removal end ===" Removal.log
|
||||
@@ -271,7 +271,7 @@ launch_user_helper_now() {
|
||||
local py="${SCRIPT_DIR}/Agent/bin/python3"
|
||||
local helper="${SCRIPT_DIR}/Agent/Borealis/agent.py"
|
||||
if [[ -x "$py" && -f "$helper" ]]; then
|
||||
(cd "${SCRIPT_DIR}/Agent/Borealis" && nohup "$py" -W ignore::SyntaxWarning "$helper" --config user >/dev/null 2>&1 & )
|
||||
(cd "${SCRIPT_DIR}/Agent/Borealis" && nohup "$py" -W ignore::SyntaxWarning "$helper" --config CURRENTUSER >/dev/null 2>&1 & )
|
||||
echo -e "${GREEN}Launched user-session helper.${RESET}"
|
||||
else
|
||||
echo -e "${YELLOW}Agent venv or helper missing; run install first.${RESET}"
|
||||
|
||||
@@ -85,6 +85,58 @@ def _argv_get(flag: str, default: str = None):
|
||||
return default
|
||||
CONFIG_NAME_SUFFIX = _argv_get('--config', None)
|
||||
|
||||
|
||||
def _canonical_config_suffix(raw_suffix: str) -> str:
|
||||
try:
|
||||
if not raw_suffix:
|
||||
return ''
|
||||
value = str(raw_suffix).strip()
|
||||
if not value:
|
||||
return ''
|
||||
normalized = value.lower()
|
||||
if normalized in ('svc', 'system', 'system_service', 'service'):
|
||||
return 'SYSTEM'
|
||||
if normalized in ('user', 'currentuser', 'interactive'):
|
||||
return 'CURRENTUSER'
|
||||
sanitized = ''.join(ch for ch in value if ch.isalnum() or ch in ('_', '-')).strip('_-')
|
||||
return sanitized
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
|
||||
CONFIG_SUFFIX_CANONICAL = _canonical_config_suffix(CONFIG_NAME_SUFFIX)
|
||||
|
||||
|
||||
def _agent_guid_path() -> str:
|
||||
try:
|
||||
root = _find_project_root()
|
||||
return os.path.join(root, 'Agent', 'Borealis', 'agent_GUID')
|
||||
except Exception:
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), 'agent_GUID'))
|
||||
|
||||
|
||||
def _persist_agent_guid_local(guid: str):
|
||||
guid = _normalize_agent_guid(guid)
|
||||
if not guid:
|
||||
return
|
||||
path = _agent_guid_path()
|
||||
try:
|
||||
directory = os.path.dirname(path)
|
||||
if directory:
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
existing = ''
|
||||
if os.path.isfile(path):
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as fh:
|
||||
existing = fh.read().strip()
|
||||
except Exception:
|
||||
existing = ''
|
||||
if existing != guid:
|
||||
with open(path, 'w', encoding='utf-8') as fh:
|
||||
fh.write(guid)
|
||||
except Exception as exc:
|
||||
_log_agent(f'Failed to persist agent GUID locally: {exc}', fname='agent.error.log')
|
||||
|
||||
if not SYSTEM_SERVICE_MODE:
|
||||
# Reduce noisy Qt output and attempt to avoid Windows OleInitialize warnings
|
||||
os.environ.setdefault("QT_LOGGING_RULES", "qt.qpa.*=false;*.debug=false")
|
||||
@@ -224,21 +276,41 @@ def _resolve_config_path():
|
||||
|
||||
# Determine filename with optional suffix
|
||||
cfg_basename = 'agent_settings.json'
|
||||
try:
|
||||
if CONFIG_NAME_SUFFIX:
|
||||
suffix = ''.join(ch for ch in CONFIG_NAME_SUFFIX if ch.isalnum() or ch in ('_', '-')).strip()
|
||||
suffix = CONFIG_SUFFIX_CANONICAL
|
||||
if suffix:
|
||||
cfg_basename = f"agent_settings_{suffix}.json"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
cfg_path = os.path.join(settings_dir, cfg_basename)
|
||||
if os.path.exists(cfg_path):
|
||||
return cfg_path
|
||||
|
||||
# Migrate legacy suffixed config names to the new canonical form
|
||||
legacy_map = {
|
||||
'SYSTEM': ['agent_settings_svc.json'],
|
||||
'CURRENTUSER': ['agent_settings_user.json'],
|
||||
}
|
||||
try:
|
||||
legacy_candidates = []
|
||||
if suffix:
|
||||
for legacy_name in legacy_map.get(suffix.upper(), []):
|
||||
legacy_candidates.extend([
|
||||
os.path.join(settings_dir, legacy_name),
|
||||
os.path.join(project_root, legacy_name),
|
||||
os.path.join(project_root, 'Agent', 'Settings', legacy_name),
|
||||
])
|
||||
for legacy in legacy_candidates:
|
||||
if os.path.exists(legacy):
|
||||
try:
|
||||
shutil.move(legacy, cfg_path)
|
||||
except Exception:
|
||||
shutil.copy2(legacy, cfg_path)
|
||||
return cfg_path
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# If using a suffixed config and there is a base config (new or legacy), seed from it
|
||||
try:
|
||||
if CONFIG_NAME_SUFFIX:
|
||||
if suffix:
|
||||
base_new = os.path.join(settings_dir, 'agent_settings.json')
|
||||
base_old_settings = os.path.join(project_root, 'Agent', 'Settings', 'agent_settings.json')
|
||||
base_legacy = os.path.join(project_root, 'agent_settings.json')
|
||||
@@ -260,15 +332,22 @@ def _resolve_config_path():
|
||||
|
||||
# Migrate legacy root configs or prior Agent/Settings into Agent/Borealis/Settings
|
||||
try:
|
||||
legacy_root = os.path.join(project_root, cfg_basename)
|
||||
legacy_names = [cfg_basename]
|
||||
try:
|
||||
if suffix:
|
||||
legacy_names.extend(legacy_map.get(suffix.upper(), []))
|
||||
except Exception:
|
||||
pass
|
||||
old_settings_dir = os.path.join(project_root, 'Agent', 'Settings')
|
||||
legacy_old_settings = os.path.join(old_settings_dir, cfg_basename)
|
||||
for legacy_name in legacy_names:
|
||||
legacy_root = os.path.join(project_root, legacy_name)
|
||||
if os.path.exists(legacy_root):
|
||||
try:
|
||||
shutil.move(legacy_root, cfg_path)
|
||||
except Exception:
|
||||
shutil.copy2(legacy_root, cfg_path)
|
||||
return cfg_path
|
||||
legacy_old_settings = os.path.join(old_settings_dir, legacy_name)
|
||||
if os.path.exists(legacy_old_settings):
|
||||
try:
|
||||
shutil.move(legacy_old_settings, cfg_path)
|
||||
@@ -356,18 +435,80 @@ class ConfigManager:
|
||||
CONFIG = ConfigManager(CONFIG_PATH)
|
||||
CONFIG.load()
|
||||
|
||||
def init_agent_id():
|
||||
if not CONFIG.data.get('agent_id'):
|
||||
host = socket.gethostname().lower()
|
||||
rand = uuid.uuid4().hex[:8]
|
||||
if SYSTEM_SERVICE_MODE:
|
||||
aid = f"{host}-agent-svc-{rand}"
|
||||
elif (CONFIG_NAME_SUFFIX or '').lower() == 'user':
|
||||
aid = f"{host}-agent-{rand}-script"
|
||||
else:
|
||||
aid = f"{host}-agent-{rand}"
|
||||
CONFIG.data['agent_id'] = aid
|
||||
def _get_context_label() -> str:
|
||||
return 'SYSTEM' if SYSTEM_SERVICE_MODE else 'CURRENTUSER'
|
||||
|
||||
|
||||
def _normalize_agent_guid(guid: str) -> str:
|
||||
try:
|
||||
if not guid:
|
||||
return ''
|
||||
value = str(guid).strip().replace('\ufeff', '')
|
||||
if not value:
|
||||
return ''
|
||||
value = value.strip('{}')
|
||||
try:
|
||||
return str(uuid.UUID(value)).upper()
|
||||
except Exception:
|
||||
cleaned = ''.join(ch for ch in value if ch in string.hexdigits or ch == '-')
|
||||
cleaned = cleaned.strip('-')
|
||||
if cleaned:
|
||||
try:
|
||||
return str(uuid.UUID(cleaned)).upper()
|
||||
except Exception:
|
||||
pass
|
||||
return value.upper()
|
||||
except Exception:
|
||||
return ''
|
||||
|
||||
|
||||
def _read_agent_guid_from_disk() -> str:
|
||||
try:
|
||||
path = _agent_guid_path()
|
||||
if os.path.isfile(path):
|
||||
with open(path, 'r', encoding='utf-8') as fh:
|
||||
value = fh.read()
|
||||
return _normalize_agent_guid(value)
|
||||
except Exception:
|
||||
pass
|
||||
return ''
|
||||
|
||||
|
||||
def _ensure_agent_guid() -> str:
|
||||
guid = _read_agent_guid_from_disk()
|
||||
if guid:
|
||||
return guid
|
||||
new_guid = str(uuid.uuid4()).upper()
|
||||
_persist_agent_guid_local(new_guid)
|
||||
return new_guid
|
||||
|
||||
|
||||
def _compose_agent_id(hostname: str, guid: str, context: str) -> str:
|
||||
host = (hostname or '').strip()
|
||||
if not host:
|
||||
host = 'UNKNOWN-HOST'
|
||||
host = host.replace(' ', '-').upper()
|
||||
normalized_guid = _normalize_agent_guid(guid) or guid or 'UNKNOWN-GUID'
|
||||
context_label = (context or '').strip().upper() or _get_context_label()
|
||||
return f"{host}_{normalized_guid}_{context_label}"
|
||||
|
||||
|
||||
def _update_agent_id_for_guid(guid: str):
|
||||
normalized_guid = _normalize_agent_guid(guid)
|
||||
if not normalized_guid:
|
||||
return
|
||||
desired = _compose_agent_id(socket.gethostname(), normalized_guid, _get_context_label())
|
||||
existing = (CONFIG.data.get('agent_id') or '').strip()
|
||||
if existing != desired:
|
||||
CONFIG.data['agent_id'] = desired
|
||||
CONFIG._write()
|
||||
global AGENT_ID
|
||||
AGENT_ID = CONFIG.data['agent_id']
|
||||
|
||||
|
||||
def init_agent_id():
|
||||
guid = _ensure_agent_guid()
|
||||
_update_agent_id_for_guid(guid)
|
||||
return CONFIG.data['agent_id']
|
||||
|
||||
AGENT_ID = init_agent_id()
|
||||
@@ -537,37 +678,6 @@ def _settings_dir():
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), 'Settings'))
|
||||
|
||||
|
||||
def _agent_guid_path() -> str:
|
||||
try:
|
||||
root = _find_project_root()
|
||||
return os.path.join(root, 'Agent', 'Borealis', 'agent_GUID')
|
||||
except Exception:
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), 'agent_GUID'))
|
||||
|
||||
|
||||
def _persist_agent_guid_local(guid: str):
|
||||
guid = (guid or '').strip()
|
||||
if not guid:
|
||||
return
|
||||
path = _agent_guid_path()
|
||||
try:
|
||||
directory = os.path.dirname(path)
|
||||
if directory:
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
existing = ''
|
||||
if os.path.isfile(path):
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as fh:
|
||||
existing = fh.read().strip()
|
||||
except Exception:
|
||||
existing = ''
|
||||
if existing != guid:
|
||||
with open(path, 'w', encoding='utf-8') as fh:
|
||||
fh.write(guid)
|
||||
except Exception as exc:
|
||||
_log_agent(f'Failed to persist agent GUID locally: {exc}', fname='agent.error.log')
|
||||
|
||||
|
||||
def get_server_url() -> str:
|
||||
"""Return the Borealis server URL from env or Agent/Borealis/Settings/server_url.txt.
|
||||
- Strips UTF-8 BOM and whitespace
|
||||
@@ -1263,6 +1373,7 @@ async def connect():
|
||||
guid_value = (data.get('agent_guid') or '').strip()
|
||||
if guid_value:
|
||||
_persist_agent_guid_local(guid_value)
|
||||
_update_agent_id_for_guid(guid_value)
|
||||
except Exception:
|
||||
pass
|
||||
asyncio.create_task(_svc_checkin_once())
|
||||
|
||||
@@ -24,7 +24,7 @@ try {
|
||||
if (-not (Test-Path $agentPy)) { throw "Agent script not found: $agentPy" }
|
||||
|
||||
$exe = if ($Console) { $py } else { if (Test-Path $pyw) { $pyw } else { $py } }
|
||||
$args = @("`"$agentPy`"","--system-service","--config","svc")
|
||||
$args = @("`"$agentPy`"","--system-service","--config","SYSTEM")
|
||||
|
||||
# Launch and keep the task in Running state by waiting on the child
|
||||
$p = Start-Process -FilePath $exe -ArgumentList $args -WindowStyle Hidden -PassThru -WorkingDirectory $scriptDir `
|
||||
|
||||
@@ -175,6 +175,8 @@ function Get-AgentServiceId {
|
||||
if (-not $AgentRoot) { $AgentRoot = $scriptDir }
|
||||
$settingsDir = Join-Path $AgentRoot 'Settings'
|
||||
$candidates = @(
|
||||
(Join-Path $settingsDir 'agent_settings_SYSTEM.json')
|
||||
(Join-Path $settingsDir 'agent_settings_CURRENTUSER.json')
|
||||
(Join-Path $settingsDir 'agent_settings_svc.json')
|
||||
(Join-Path $settingsDir 'agent_settings_user.json')
|
||||
(Join-Path $settingsDir 'agent_settings.json')
|
||||
|
||||
Reference in New Issue
Block a user