Two issues raised by @minasarustamyan:
1. head_start loop rendered BEFORE <meta charset="UTF-8"> in both
base.html and base_login.html. HTML5 requires the charset declaration
within the first 1024 bytes; a long operator-injected snippet could
push it past that window, dropping browsers into locale-default
encoding (historically a UTF-7 charset-confusion XSS vector). Move
the head_start loop after charset + viewport meta tags — vendor
hooks (GTM dataLayer init, etc.) still install before any CSS/JS, the
two required meta tags just come first.
2. `entry.get("enabled") is False` matched only the Python False
singleton — `enabled: "false"` (quoted YAML string), `enabled: 0`,
`enabled: "no"` etc. all slipped through (bool("false") == True in
Python). For what is meant to be a kill switch on admin-injected JS,
an operator who fat-fingers the quoting would silently leave the
snippet live. Add `_custom_script_enabled()` coercion helper that
honours quoted booleans, numeric 0, and the usual `no`/`off`/`""`
variants. Default-on for missing / None to preserve the
default-enabled field semantics.
13 new parametrized tests cover every YAML truthy-shape the operator
might paste. Render test now asserts head_start lands after both
required <meta> tags.
Add a generic, placement-aware mechanism for operators to inject HTML/JS
into every page that extends base.html or base_login.html. Each entry
takes name, enabled, placement (head_start | head_end | body_end), and
html. Replaces the need for per-vendor helpers when shipping feedback
widgets, analytics, or error-capture snippets.
Trust boundary mirrors the existing instance.logo_svg / instance.overview
pattern — admin-only, rendered with `| safe`. Resolved by
app/instance_config.py::get_custom_scripts(), surfaced in
/admin/server-config via _KNOWN_FIELDS["instance"]. Empty default keeps
the OSS vendor-neutral; sample Marker.io block ships commented out in
config/instance.yaml.example as the canonical example.
The /home onboarding page already has a visible manual "Step 3 — create
your workspace folder" instructing the user to `mkdir -p ~/<dir> && cd
~/<dir>` BEFORE pasting the install script into Claude Code. The pasted
script's step 2 then re-ran the same mkdir+cd, which silently overrode
an intentional alternate install path (e.g. user cd'd to
~/work/agnes-prod on purpose) and was redundant on the default path.
Step 2 now verifies the user is in `$HOME/<workspace_dir>` via `pwd`.
On mismatch it stops and asks the user to either re-paste from the
correct folder or reply `install here` to accept the current cwd.
Never auto-creates a folder.
Step 9 (restart Claude Code) references the install directory confirmed
in step 2 instead of a hardcoded `~/<workspace_dir>`, so users on a
custom path see accurate guidance.
- instance.brand (env AGNES_INSTANCE_BRAND, default "Agnes") +
instance.workspace_dir replace hard-coded "Agnes" / "~/Agnes" across
/home, /setup, /setup-advanced, /login, /install, /me/debug, and the
Claude Code clipboard setup script. Terraform-friendly env override;
defaults preserve existing Agnes branding.
- Explicit "create workspace folder" step on /home (OS-tabbed mkdir+cd)
+ same step baked into the clipboard script as step 2. Drops the
implicit assumption that `agnes init --workspace .` lands in a
sensibly-cd'd shell.
- Final "Restart Claude Code" step in the setup script (unconditional,
between connectors and Confirm) so freshly-installed plugins, MCP
servers, and SessionStart hooks load on the next Claude Code session.
- Asana reverted from hosted Remote MCP back to PAT + raw REST against
app.asana.com/api/1.0. MCP envelope shape consumed ~5x tokens per
call; the PAT path lets the agent read flat REST fields. Existing
MCP registration is detected and the user is asked whether to remove
it (default Y, with benefits listed: token cost, no third-party hop,
no OAuth refresh dance, deterministic envelope shape).
- Atlassian connector instructs picking the longest API-token expiry
(today "1 year") to cut re-mint friction. No public query-parameter
hook exists on id.atlassian.com to pre-select expiry, so the prompt
documents the manual click and acknowledges that limitation.
- Uniform ✅ / ❌ per-connector marker contract (Asana, GWS, Atlassian)
for the Confirm summary to grep. Each connector now ends with a
Claude-driven end-to-end test that uses Claude Code's own bash to
exercise the stored credential and prints
"✅ <Connector> integration verified — ..." (or the failure variant).
- tests/test_web_ui.py: smoke tests for all authenticated web pages (login, dashboard, catalog, corporate-memory, activity-center, admin/tables, admin/permissions)
- tests/test_jira_service.py: unit tests for extract_init and update_meta in the Jira connector
- tests/test_instance_config.py: verifies get_instance_name() returns a string when config file is absent
- tests/test_orchestrator.py: concurrent rebuild test asserting rebuild succeeds while a read-only connection holds the analytics DB