Files
Skald-Circle/web/css/page-shell.css
T
dguiducci 165af19774
Nightly Build / build (push) Successful in 6m58s
tic: run per-user under a system-agent scheduler, with a run log
Reframe TIC from an ownerless global loop into a per-user system agent.
The events it reads live in each user's own encrypted mcp_events, the
connectors that produced them run in that user's container, and the
notifications go to that user's hub — so the previous design (built
against the ownerless Conversation bundle, writing into system.db and
notifying a hub with no subscribers) was inert by construction.

Core changes
- TicManager owns no timer and no user list. It now exposes
  run_for(user_id, pool, sessions, hub): one tick for one user, over
  deps unpacked from that user's UserContext. Removed from the
  Conversation bundle; Skald::tic_manager() is gone.
- New spawn_system_agents in wiring.rs: one instance-wide loop, spawned
  post-construction with a Weak<Skald> (like spawn_user_lifecycle).
  Each pass walks the directory and runs TIC for one user at a time —
  sequential, because a pass is N container round-trips and N LLM calls
  nobody is waiting on. A ConfigKeyUpdated on the interval key cuts the
  current wait short; enabled is re-read per pass.
- A user whose database is still locked is skipped (normal, not an
  error): the pool is the unlock token, so a user who hasn't logged in
  since restart has no readable events and nowhere to record a skip.
- The configured tic.security_group is re-checked per user through
  run_context::reconcile_group_for_user — a restricted member never
  gets a tool set their role wouldn't grant; unconfigured starts from
  role_default_run_context, never None (None = catch-all = wider).
- New system_agent_runs owner table (no user_id column — the file is
  the owner): start/finish split so a crash leaves a visible 'running'
  row, swept to 'failed' by the next start; safe because the scheduler
  is sequential and single-instance. An idle tick writes nothing.
- counting_notify wraps the notify tool so the run log can report
  notifications emitted without the tool knowing it's counted.
- The session's event channel is drained by a spawned task instead of
  a dropped receiver — the translator awaits its sends and would wedge
  at capacity.

EventLog::{Persist,Discard} on McpManager::new
- mcp_events is an owner table and its only reader (TIC) is per-user,
  so an event is something that happened to someone. The per-user
  runtime gets Persist; the ownerless global runtime gets Discard (its
  pool is system.db, rows would be unattributable and unread).

API + UI
- GET /api/system-agents/runs: the caller's own run history, scoped
  through require_context with no admin override (same promise as the
  rest of the private pool).
- web/components/system-agents.js replaces tic-sessions.js. The old
  #tic debug page inferred runs from leftover ephemeral sessions; the
  new #system-agents page (sidebar group 'extensions', visible to
  everyone — the data is the caller's own) reads the real run log.
- i18n: tic.* keys replaced with system_agents.* in en/it/fr.

Docs
- New docs/system-agents.md (user-facing: what TIC does, why it runs
  per person, why a run can be missing). Updated docs/settings.md and
  docs/index.md.
- agents/tic/AGENT.md reframed per-user: events are that person's,
  memory is user-memory/ (private) — never shared-memory/.
- CLAUDE.md records the system-agents design and the EventLog seam.
2026-07-27 11:39:13 +01:00

119 lines
2.6 KiB
CSS

/* ── Custom element display + layout shell ──────────────────────────────────── */
tasks-page {
display: none; /* toggled by JS */
flex-direction: column;
flex: 1;
min-width: 0;
min-height: 0;
overflow: hidden;
border-right: 1px solid var(--toolbar-border, #e5e9f0);
}
task-running-section,
task-cron-jobs-section,
task-scheduled-section,
task-history-section {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
min-width: 0;
}
llm-providers-page,
models-hub-page {
display: none; /* toggled by JS */
flex: 1;
min-width: 0;
overflow-y: auto;
border-right: 1px solid var(--toolbar-border, #e5e9f0);
}
models-llm-section,
models-transcribe-section,
models-image-section {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
min-width: 0;
}
projects-page {
display: none; /* toggled by JS */
flex-direction: column;
flex: 1;
min-width: 0;
min-height: 0;
overflow: hidden;
border-right: 1px solid var(--toolbar-border, #e5e9f0);
}
project-list-section,
project-board-section {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
min-width: 0;
}
session-detail-page,
system-agents-page,
file-viewer-page,
tool-detail-page {
display: none; /* toggled by JS */
flex-direction: column;
flex: 1;
min-width: 0;
min-height: 0;
overflow: hidden;
border-right: 1px solid var(--toolbar-border, #e5e9f0);
}
users-page,
roles-page,
shared-folders-page,
connectors-page,
connector-detail-page,
plugins-page,
plugin-catalog-page,
plugin-detail-page,
plugin-page-host,
marketplace-page,
catalog-page,
profile-page {
display: none; /* toggled by JS */
flex-direction: column;
flex: 1;
min-width: 0;
min-height: 0;
overflow: hidden;
border-right: 1px solid var(--toolbar-border, #e5e9f0);
}
/* Plugin-contributed page fragments mount inside the host with the `plugin-id`
attribute set — make them fill the host column (the host is `flex:1`, but a
bare custom element has no default sizing). Generic across all plugins. */
plugin-page-host > [plugin-id] {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
min-height: 0;
overflow: hidden;
}
/* ── Workspace placeholder ──────────────────────────────────────────────────── */
.app-workspace {
display: flex;
flex: 1;
min-width: 0;
align-items: center;
justify-content: center;
color: var(--placeholder-color, #94a3b8);
font-size: 0.9rem;
}