Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/memory/architecture/frontend.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
**Library system-install section (ent#126):** the install surface is a third stacked `<section id="systems">` on `views/Library.vue`, between Agent Templates and Skills. It originally shipped as a `?tab=`-driven strip on `Templates.vue`; ent#263 renamed that page to **Library** ("installable assets for your fleet") and chose stacked sections with jump anchors over tabs/filter pills, so ent#126 conforms to the page's model rather than reintroducing a competing one — the ordering puts Systems next to Agent Templates because both *install agents* (a template creates one, a manifest creates a wired fleet), leaving Skills last as the one that configures agents that already exist. The section is hidden outright below `creator` (gated on `hasMinRole('creator')`, mirroring `POST /api/systems/deploy`) rather than shown-and-disabled, since a browse surface gains nothing from a dead panel. It hosts `components/systems/` — `SystemInstallPanel` (bundled cards / upload / paste + Preview + Deploy) → `ManifestPreview` (agents, permission topology, schedules, blockers, acknowledgement gate) → `DeployResult` (all five `status` values) — over the new domain-scoped `stores/systems.js` (Invariant #6: a *System* is a manifest-deployed name-prefix group, a *System View* is a saved tag filter — different domains). The store's `normalizeError` collapses six response shapes and switches on `status`, never the HTTP code (`partial`/`invalid` are 200; `failed` is **500 with the full report as the body**), and binds `preview` to `previewedText` so Deploy is disabled after an edit. Plain `<textarea>`: the orphaned monaco `components/YamlEditor.vue` stayed unrevived because prod CSP is `script-src 'self'` with no `unsafe-eval`/`worker-src` while the dev CSP allows `unsafe-eval` — and was deleted, with its companion `EditorHelpPanel.vue`, in the #2492 dead-component sweep (the CSP rationale is permanent, so a revival would be a rewrite either way). `views/Dashboard.vue` reads `?view=`/`?tags=` so a fresh deploy lands filtered to its own fleet. **ent#454** adds the section's gated second half: `SystemTeardownPanel` → `TeardownPreview` → `TeardownResult`, rendered only when `enterprise_features` carries `system_teardown` (the verb is an entitlement-gated module, so an OSS build's Systems section is unchanged) on top of the same `creator` gate. Its preview is a **checklist**, not a list: a member the server matched by name alone (`evidence: prefix`) may belong to a sibling system whose name shares this one's prefix, the server cannot tell which, so the badge plus the per-agent opt-out is the over-capture escape hatch — and the checked list is sent back on execute to be intersected with freshly re-resolved membership. Remove is gated on four conditions (preview current for the typed name, membership verified, consequence acknowledged via ent#126's reused `:acknowledged`/`update:acknowledged` contract under its own `teardown-ack-checkbox` id — the pattern is shared, the address deliberately is not, since both panels render on one page, ≥1 agent checked) and states which is missing. `ManifestPreview`'s "there is no un-deploy" warning now branches on the same entitlement rather than being retired on every edition — it is still exactly true where the module is absent.
**Top-nav IA — Library (trinity-enterprise#263):** the former Templates nav entry is one **Library** entry (`views/Library.vue`, `/library`) — a single surface for installable assets: an **Agent Templates** section (the existing Starter/GitHub/Custom card grids) and a **Skills** section (`components/LibrarySkillsSection.vue` — fleet-level browse over the shared skills library, backed by the fleet-scoped `stores/skillsLibrary.js`, deliberately separate from the agent-scoped `stores/skills.js` whose KeepAlive'd Skills-tab consumer never unmounts on nav-away). Stacked sections with in-page jump anchors (no `?kind=` machinery); per-section failure isolation; per-kind empty states. The skills half reads only the existing `GET /api/skills/library` (+`/status`) surfaces and renders the #183 contract via the shared `components/skills/` chips seam consumed by both the Library and the per-agent Skills tab; assignment stays on Agent Detail (ent#182: one skill model). **No new backend endpoints.** Legacy `/templates` redirects (query+hash-preserving). See [library-page.md](../feature-flows/library-page.md).

**Agent Skills tab (trinity-enterprise#754):** one tab replaced Playbooks (the agent's own skills, with Run) and the library-only Skills tab. `components/skills/SkillsTab.vue` renders **Own** and **Shared** sections of fixed-size `SkillCard.vue`s (every slot a fixed height, so content never resizes a card; verb errors replace the description slot); the library picker is `SkillAssignModal.vue`, sets open in a `BaseModal` around the unchanged `AgentSkillSets.vue`, and the #2914 conflict / warnings / superseded text open in `SkillDetailsModal.vue`. Every rule — exactly-one-section, the Run predicate (running + LIVE list + `user_invocable`), the gate line (a kind, never a person), the role/ghost/system controls, the author-mode chip relabel — is the pure `utils/skillCards.js`; data is `stores/skills.js` (+ the agent's own listing, `loadAgentList` over `GET /playbooks?last_known=true`, and `runSkill` with `async_mode` because `api.js` times out at 30 s) and the agent-scoped `stores/skillGates.js` (map first, hook probe after; write-then-reload keyed to the agent written). The tab is visible to everyone with access; `TAB_ALIASES` (now exported from `utils/agentTabs.js`) sends `?tab=playbooks` to it. `BaseBadge` gained `size="sm"` (11px) for the mode chip. `skills/ExecutionGateMarker.vue` marks a self-approved run on the Tasks row, the execution page and the Workspace reply — see [skills-tab.md](../feature-flows/skills-tab.md).
**Agent Skills tab (trinity-enterprise#754):** one tab replaced Playbooks (the agent's own skills, with Run) and the library-only Skills tab. `components/skills/SkillsTab.vue` renders **Own** and **Shared** sections of fixed-size `SkillCard.vue`s (every slot a fixed height, so content never resizes a card; verb errors replace the description slot); the library picker is `SkillAssignModal.vue` (its draft rules — Select all / Deselect all over the listed rows only, and the confirm before a save drops an approval requirement — are the pure `utils/skillAssignDraft.js`, trinity-enterprise#539), sets open in a `BaseModal` around the unchanged `AgentSkillSets.vue`, and the #2914 conflict / warnings / superseded text open in `SkillDetailsModal.vue`. Every rule — exactly-one-section, the Run predicate (running + LIVE list + `user_invocable`), the gate line (a kind, never a person), the role/ghost/system controls, the author-mode chip relabel — is the pure `utils/skillCards.js`; data is `stores/skills.js` (+ the agent's own listing, `loadAgentList` over `GET /playbooks?last_known=true`, and `runSkill` with `async_mode` because `api.js` times out at 30 s) and the agent-scoped `stores/skillGates.js` (map first, hook probe after; write-then-reload keyed to the agent written). The tab is visible to everyone with access; `TAB_ALIASES` (now exported from `utils/agentTabs.js`) sends `?tab=playbooks` to it. `BaseBadge` gained `size="sm"` (11px) for the mode chip. `skills/ExecutionGateMarker.vue` marks a self-approved run on the Tasks row, the execution page and the Workspace reply — see [skills-tab.md](../feature-flows/skills-tab.md).

**Library tabs + fleet assignment read (trinity-enterprise#384):** the three stacked sections become an `OverflowTabs` strip — **Agent Templates · Systems · Skills** — deliberately reversing ent#263's stacked-sections choice for *all* sections at once rather than special-casing Skills (a tabs-plus-stacked hybrid would leave the page with two competing models). `?tab=` URL state, with a `route.query.tab` watch so the RENDER follows the URL for any navigation that changes it — an external link, a deep link, a history entry. Reading the query once at setup (`Operations.vue`'s shape) leaves those cases changing the address bar while the panel stays put. Tab *clicks* use `router.replace`, so switching tabs deliberately does not push history entries and Back leaves the page rather than walking back through tabs; the legacy `#agent-templates`/`#systems`/`#skills` anchors migrate to the matching tab and are then cleared, with `?tab=` winning over a hash. The Systems tab stays creator-gated with **both** arms of a late-role watch, since `stores/auth.js` reports `user` until `/api/users/me` lands and a creator hard-loading `?tab=systems` would otherwise land on Templates and never recover. Panels **lazy-mount-once** (`v-if` visited + `v-show` active): a plain `v-if` refetches status+library+assignments on every switch — the #1109 teardown rationale does not apply, `stores/skillsLibrary.js` owns no poll — while a plain `v-show` mounts Skills and Systems for a Templates-only visitor; it also preserves `SystemInstallPanel`'s editor state. Each skill block now lists **which agents already hold it** (chips → `/agents/{name}?tab=skills`, bounded with a counted overflow), plus a bounded **orphaned-assignments** list for rows whose skill left the library — ent#237's revocation model is "cut a new tag without the offending skill", after which the operator's first question is *who still has it*, and a page keyed by the library listing answers "nothing" forever. Assignment itself stays a **write on the per-agent Skills tab** (ent#182: one skill model); the Library gained a read, not a second write path — the assign/unassign writes were split to ent#386 and delivery semantics remain ent#385. **OSS-core by decision (ent#384): deliberately ungated — no `requires_entitlement`, logic stays in the OSS tree.** Recorded explicitly because CLAUDE.md's default for an enterprise-tracker feature is *gated unless ruled otherwise*, so the ruling must never be inferred later from the mere fact that it merged (the ent#326 discipline). Rationale: generic fleet telemetry over OSS tables, on a page and a skills stack that already shipped OSS-core (ent#183/#236/#237/#263/#126). See [library-page.md](../feature-flows/library-page.md).

Expand Down
10 changes: 6 additions & 4 deletions docs/memory/feature-flows/skill-assignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

| Date | Changes |
|------|---------|
| 2026-10-22 | **trinity-enterprise#539** — the Assign skills dialog gains **Select all** / **Deselect all**, which build the draft over the listed rows only (rules in `utils/skillAssignDraft.js`; set rows, deprecated skills and unlisted assignments are left as they are), and **Save** confirms first when it would drop an approval requirement. Writes unchanged: still the one bulk PUT. Line cites below re-pointed. See [skills-tab.md](skills-tab.md). |
| 2026-10-08 | **trinity-enterprise#754** — the assignment UI is now the **Shared skills** section of the merged Skills tab ([skills-tab.md](skills-tab.md)), visible to everyone with access; the controls stay owner/admin-only. `SkillsPanel.vue` is deleted: assigned rows became cards, the library picker moved into `SkillAssignModal.vue` ("Assign skills"), the #2914 conflict / delivery warnings / superseded text open in `SkillDetailsModal.vue`, and sets show as chips with `AgentSkillSets.vue` in a "Manage sets" dialog. Routes and store unchanged. |
| 2026-10-07 | **trinity-enterprise#753** — every route here that writes `agent_skills` (PUT, POST, DELETE, the inject Sync, POST/DELETE set) now also keeps the agent's skill gate map in line (`_sync_gates` → `skill_gate_map_service`), and answers with a `gates` block (`gate_defaults` applied/removed, `gates_removed`, `gates_kept`, or null). A library skill whose metadata says `approval: recommended` is gated BEFORE its package is delivered; a gate on an unassigned skill goes only once its package removal completed, and an explicit gate only when a person unassigned it. See [skill-gate.md](skill-gate.md) §6. |
| 2026-09-30 | **ent#672** — a deprecated library skill is marked and its assignment says so. `SkillsPanel.vue` shows a `deprecated` badge + "Superseded by …" line on the assigned rows (an agent already holding one shows it on load) and on the picker rows (before the tick); `POST` / `PUT` still succeed and their `delivery.skills[name].warnings` carries `deprecated[:<successor>]`, rendered as a warning line under the save note; after a manual Sync the per-skill warning list says it in words. See "Deprecated skills" below and [skill-injection.md](skill-injection.md). |
Expand All @@ -29,7 +30,7 @@ As an agent owner, I want to assign platform skills to my agents so that they fo
## Entry Points

### User Configuration (UI)
- **Component**: `src/frontend/src/components/skills/SkillAssignModal.vue:75-77` - "Save assignments" (bulk PUT), opened by **Assign skills** in the Skills tab's Shared section (`SkillsTab.vue:136`)
- **Component**: `src/frontend/src/components/skills/SkillAssignModal.vue:107-130` - "Save assignments" (bulk PUT), with **Select all** / **Deselect all** on the list's header row (`:28-54`), opened by **Assign skills** in the Skills tab's Shared section (`SkillsTab.vue:136`)
- **Tab Access**: Agent Detail page -> Skills tab, visible to everyone with access since trinity-enterprise#754; the assignment controls (Assign skills, Manage sets, Sync now, Unassign) are for the owner or an admin, never on the system agent. See [skills-tab.md](skills-tab.md)

### Backend API
Expand All @@ -49,7 +50,7 @@ Since trinity-enterprise#754 the assignment surface is the **Shared skills** sec

| Control | File | Call |
|---|---|---|
| **Assign skills** → tick → **Save assignments** | `components/skills/SkillAssignModal.vue:134-146` | `store.saveAssignments(draft)` → `PUT /api/agents/{name}/skills` |
| **Assign skills** → tick (or **Select all** / **Deselect all**) → **Save assignments** | `components/skills/SkillAssignModal.vue:236-337` (a save that drops an approval requirement confirms first, `:290-323`) | `store.saveAssignments(draft)` → `PUT /api/agents/{name}/skills` |
| **Unassign** on a Shared card | `components/skills/SkillsTab.vue:546-564`, after its confirm (`:566-578`) | `store.saveAssignments(individualNames − name)` → the same PUT |
| **Unassign library skill** (#2914 conflict) | `components/skills/SkillDetailsModal.vue:31-41` → `SkillsTab.onUnassign` | the same PUT |
| **Manage sets** | `components/skills/AgentSkillSets.vue` in a `BaseModal` (`SkillsTab.vue:271-276`) | `store.assignSet` / `unassignSet` → `POST` / `DELETE /api/agents/{name}/skill-sets/{set}` |
Expand All @@ -63,7 +64,8 @@ The picker keeps its ent#235 / ent#530 / ent#672 / #2914 behaviour: the draft is

- `library`, `libraryStatus`, `assigned` (the rows, with `individual` / `via_sets` / `delivery_status`), `sets`, `librarySets`; `injectionResults` + `lastInjectionAt` (the last Sync, per skill); `lastDelivery` (the last save's #2703 report).
- Derived: `individualNames` (the draft's source — what the bulk PUT replaces), `setOnlyNames` (locked rows), `conflictNames` (#2914), `emptyReason` (`library_unconfigured` / `library_empty` / `none_assigned`).
- The picker's dirty check compares the sorted draft with `individualNames` (`SkillAssignModal.vue:120-124`); the draft resets only when that set changes (`:132`).
- The picker's dirty check compares the sorted draft with `individualNames` (`SkillAssignModal.vue:197-201`); the draft resets only when that set changes (`:209`).
- Select all / Deselect all (trinity-enterprise#539) are pure rules in `utils/skillAssignDraft.js` (`bulkSelection`, read at `SkillAssignModal.vue:223-230`): they change only the draft, act only on the listed rows, never move a set row, never tick a deprecated skill or a name the PUT refuses, and keep an assignment the dialog does not list. Before the PUT, a save that removes anything re-reads the gate map and `gatedRemovals` decides whether Save confirms (`:290-318`).

### API Calls

Expand All @@ -79,7 +81,7 @@ lastDelivery.value = saved?.delivery ?? null
const { data } = await api.get(`/api/agents/${agentName.value}/skills`) // re-read the rows
```

The dialog then emits `saved` with `deliveryText(lastDelivery, {saved: true})` and `deprecationText(lastDelivery)` (`SkillAssignModal.vue:134-146`); the tab shows it on the Shared line and arms the Sync emphasis only when delivery did not land.
The dialog then emits `saved` with `deliveryText(lastDelivery, {saved: true})` and `deprecationText(lastDelivery)` (`SkillAssignModal.vue:325-337`); the tab shows it on the Shared line and arms the Sync emphasis only when delivery did not land.

**`inject()`** (`stores/skills.js:403-432`): `POST /api/agents/{name}/skills/inject` (a repair: `force` server-side). Results are stored per skill (`injectionResults`), then the rows are re-read so a #2914 verdict that was set or cleared shows at once. A 409 reads "A skill sync is already running for this agent. Try again in a moment."

Expand Down
Loading
Loading