Commit Graph
7 Commits
Author SHA1 Message Date
dguiducci 080ea736e4 feat: signpost the virtual memory roots inside the container, instead of leaving them absent
Nightly Build / build (push) Successful in 7m38s
`user-memory/` and `shared-memory/` live in SQLite, so nothing of them existed on
disk — and that nothing was worse than it looks. `cat user-memory/x.md` returned a
bare ENOENT, which a model reads as "the note is missing" rather than "wrong door";
and `mkdir -p user-memory && echo … > user-memory/x.md` *succeeded*, writing a real
file into the home that no reader ever visits (every reader goes to `memory_docs`)
and that the next `ls` then confirms as if it had worked.

Each root now gets a read-only bind mount holding a README that names the tools to
use instead. Read-only as a mount rather than as a mode: the container user has
passwordless sudo, so a chmod would be a suggestion, while `:ro` holds — remounting
needs CAP_SYS_ADMIN. Verified in a scratch container: write, sudo write, sudo chmod,
sudo mount -o remount,rw and sudo rm all fail. And a README rather than an empty
directory, because "Permission denied" is an error, not an instruction — models
answer it by reaching for sudo; the README puts the correction in the same directory
the failing command just named.

The mounts are deliberately not part of `UserFs`: they back no agent path and the
host-side fs-tools must never resolve into them. They reach existing containers as a
fourth self-heal axis in `reusable()`, not as an IMAGE_TAG bump — the image is
unchanged, and a bump would make every installation rebuild it to fix a mount.

The matching half is in `classify_memory`, which now strips the home spellings
(`./`, `~/`, `/root/`) before matching the root. Without it `~/user-memory/x.md`
missed the match and fell through to the disk router — becoming exactly the
invisible physical file the signpost exists to prevent.

`agents/common/memory.md` says the rule outright: the stores are reachable only
through the file tools and `memory_search`, never through `execute_cmd`.
2026-08-02 22:34:24 +01:00
dguiducci 6040f9a339 honcho: plugin web pages with i18n, defer plugin detail to custom admin page
Nightly Build / build (push) Failing after 6m13s
2026-07-20 15:29:26 +01:00
dguiducci 0e4d38eefc Move CI scripts scripts/ -> ci/ (scripts/ è in gitignore)
Nightly Build / build (push) Failing after 6m4s
2026-07-19 23:54:22 +01:00
dguiducci aba231b641 Fix: gitignore scripts/package.sh e verify-version.sh, tolti da .gitignore
Nightly Build / build (push) Has been cancelled
2026-07-19 23:53:44 +01:00
dguiducci e6c4e202a4 feat(mcp): OAuth per-user connectors (§15) — providers, PKCE copy-paste flow, env credential delivery
- oauth_providers registry table (per-provider client creds) + db/oauth_providers.rs
- mcp/oauth.rs: authorization-code + PKCE S256, RAM-only TTL'd flow store, copy-paste consent
- mcp/install.rs + verify.rs: connector file install + manifest verification
- activate persists a pending row (needs_oauth); /mcp/oauth/start + /complete exchange code for refresh token
- credential delivery via env var on docker exec (google_authorized_user JSON), never on disk
- mcp_catalog/mcp_user_servers: additive OAuth columns (ensure_column), catalog_name/oauth_provider/deliver_json bare TEXT snapshots
- frontend: connector-detail.js (OAuth login panel), shared/connector-common.js, connectors.js admin Sign-in providers modal
- API: /mcp/providers (admin OAuth creds), /mcp/oauth/start|complete
- .gitignore: add /homes/ (instance data), /connectors/, /reset.sh; drop stale /secrets/
2026-07-17 21:47:51 +01:00
dguiducci bcd8f7b5c0 feat(mcp): connector marketplace + split the Connectors surface (§7/§14/§15)
Fills a gap the blueprint names: the admin had to hand-author every
`mcp_catalog` entry. A remote feed of vetted connectors now proposes them
and the admin installs — the feed is *consultative*, so §14's risk axis is
untouched and the trust anchor stays on the box.

Marketplace client (`src/frontend/api/marketplace.rs`):
- Fetches the feed server-side (it sends no CORS headers) and caches it;
  icons are proxied for the same reason.
- Verifies every declared SHA-256 before writing, fail-closed and
  all-or-nothing. Feed-supplied paths are refused if they escape
  `./scripts/<id>/`. Importing an `mcp_local` entry still demands the
  admin-only `mcp.register_local_script`.
- Translates the feed's vocabulary into Skald's: `user`→`per_user`,
  `mcp_local`→`local_script`. Scope is read, never inferred from transport
  (a remote connector can be per-user — that is what `mcp.register_remote`
  is for), and an unreadable `type` fails closed to the answer needing more
  authority. The feed's `llm_short_description` maps to `description`, the
  column `render_mcp_list` puts in front of the LLM for `activate_tools()`.
- Feed URL is config (`marketplace.url`), not a constant: an on-premise
  product must not hard-require reaching one vendor's host.

Two silent failures found while wiring it:
- `transport_of` maps anything unknown to Stdio, so the feed's
  `streamable-http` would have tried to spawn a command. Normalised on import.
- Some servers want their key as a query param, not a bearer header, and say
  so with a `{key}` placeholder. Substituted at connect time in
  `global_row_spec`/`user_row_spec` — never at rest, so the key stays in its
  own column and the stored URL stays a template.

Pages, split by the question each answers:
- Connectors — what runs (`UserMcpView` = global ∪ per-user) and what I can
  add. Same page for everyone; the admin just has more verbs. One Available
  list with the verb per row: `per_user`→Activate, `global`→Enable globally.
  Enabling a global is the admin's counterpart to activating a per-user one,
  so the catalog picker dropdown is gone — the entry comes from the row.
- Connector Catalog (admin) — what this box offers. One `Add connector`
  with two sources: marketplace first (vetted, hashed), manual second
  (unvetted by nature) — the order mirrors the trust model.
- Marketplace (admin) — reached from the catalog, not the sidebar: it is a
  destination of an action, not a place.

`available()` no longer returns `McpGlobalServerRow`: that row carries
`api_key` and this view now reaches every logged-in user. A slim `GlobalView`
crosses instead, and an admin sees every global (with `can_use` marking their
own) so one enabled for someone else stays manageable.

Also fixes `connectors-page` having no CSS rule at all — every sibling page
has one, so it never got `flex: 1` and left an empty column beside it.
2026-07-16 18:52:59 +01:00
dguiducci 38494a85a9 First Version 2026-07-10 15:02:09 +01:00