You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
π feat: always-apply frontmatter: auto-prime skills every turn (danny-avila#12746)
* π refactor: Rebase always-apply work onto merged structured-frontmatter columns
Phase 6 (disable-model-invocation / user-invocable / allowed-tools)
landed first on feat/agent-skills. Reconcile this branch with the new
mainline:
- Thread alwaysApplySkillPrimes through unionPrimeAllowedTools alongside
manualSkillPrimes, applying the combined MAX_PRIMED_SKILLS_PER_TURN
ceiling before loading tools.
- Add `_id` to ResolvedAlwaysApplySkill to match Phase 6's
ResolvedManualSkill shape (read_file name-collision protection).
- Register 'always-apply' in ALLOWED_FRONTMATTER_KEYS / FRONTMATTER_KIND
so Phase 6's validator recognizes it.
- Drop frontmatter from the listSkillsByAccess projection; the backfill
helper remains as defensive code but its read path is no longer
exercised on summary rows (no legacy rows exist β the branch never
shipped), saving ~200KB per page.
- Retire the corresponding "backfills legacy on summaries" test.
- Plumb listAlwaysApplySkills through the JS controllers + endpoint
initializer so the always-apply resolver sees a real DB method.
* π§Ή fix: Dedupe manual/always-apply overlap, share YAML util, tidy comments
Addresses review findings:
- Cross-list dedup: when a user $-invokes a skill that is also marked
always-apply, the always-apply copy is now dropped so the same
SKILL.md body never primes twice in one turn. Manual wins (explicit
intent, closer to the user message). Dedup runs in both
initializeAgent (so persisted user-bubble pills stay in sync) and
injectSkillPrimes (defense-in-depth at splice time). New test cases
cover single-overlap, partial-overlap, and dedup-before-cap.
- DRY: extract stripYamlTrailingComment to
packages/data-schemas/src/utils/yaml.ts; packages/api/src/skills/import.ts
now imports the shared helper. Also drop the redundant inner
stripYamlTrailingComment call inside parseBooleanScalar β the call
site already strips.
- Mark injectManualSkillPrimes as @deprecated in favor of
injectSkillPrimes (kept for external consumers of @librechat/api).
- Document SKILL_TRIGGER_MODEL as forward-looking plumbing for the
model-invoked path rather than leaving it as a bare unused export.
- Replace the stale "frontmatter is included" comment on
listSkillsByAccess with an accurate explanation of why it was
intentionally excluded.
* π fix: Include always-apply primes in skillPrimedIdsByName + clear alwaysApply on body opt-out
Two bugs flagged by Codex review:
P1 (read_file): `manualSkillPrimedIdsByName` only carried manual-invocation
primes, so an always-apply skill with `disable-model-invocation: true`
was blocked from reading its own bundled files, and same-name collisions
could resolve to a different doc than the one whose body got primed.
- Rename `buildManualSkillPrimedIdsByName` β `buildSkillPrimedIdsByName`
(accepts both manual + always-apply prime arrays).
- Rename the configurable field `manualSkillPrimedIdsByName` β
`skillPrimedIdsByName` throughout the plumbing (skillConfigurable.ts,
handlers.ts, CJS callers, tests).
- Overlap resolution: manual wins on the rare edge case where the same
name appears in both arrays (upstream dedup should prevent this, but
defensive merging treats manual as authoritative).
- New tests: (1) gate-relaxation fires for always-apply primes, (2) `_id`
pinning works for always-apply same-name collisions.
P2 (updateSkill): when a body update had no `always-apply:` key,
`extractAlwaysApplyFromBody` returned `absent` and the column was left
untouched. A skill that was once `alwaysApply: true` would keep
auto-priming even after its SKILL.md no longer declared the flag.
- Treat `absent` as a positive "not always-apply" declaration when the
body is explicitly submitted; flip the column to `false`.
- Explicit top-level `alwaysApply` still wins (three-source precedence
unchanged).
- New tests: body removes key β false, body has no frontmatter at all β
false, explicit + body-without-key β explicit wins.
* π§΅ refactor: Collapse duplicate prime types + tighten parse + test hygiene
Sanity-check review follow-ups:
- Collapse `ResolvedManualSkill` / `ResolvedAlwaysApplySkill` into a
single `ResolvedSkillPrime` canonical interface with two backward-
compatible type aliases. Both resolvers feed the same pipeline stages
(injectSkillPrimes, unionPrimeAllowedTools, buildSkillPrimedIdsByName);
the per-source distinction lives on `additional_kwargs.trigger`, not
on the resolver output.
- Move the `always-apply` branch in `parseFrontmatter` to operate on the
raw post-colon text. The outer `unquoteYaml` was fine today because
it's idempotent on non-quoted strings, but running it twice (once per
line, once after stripping the inline comment) would be fragile if the
unquoter ever grows richer YAML-escape handling.
- Add the missing `alwaysApplyDedupedFromManual: 0` field to the
`injectSkillPrimes` mocks in `openai.spec.js` and `responses.unit.spec.js`
so they match the full `InjectSkillPrimesResult` contract.
- Insert the blank line between the `unionPrimeAllowedTools` and
`resolveAlwaysApplySkills` describe blocks.
* π§ fix(tsc): Cast mock.calls via `unknown` for strict tuple destructure
`getSkillByName.mock.calls[0]` is typed as `[]` by jest's generic default;
a direct cast to `[string, ..., ...]` fails TS2352 under `--noEmit` even
though the runtime shape matches. Go through `as unknown as [...]` like
the earlier test in the same file so CI's type-check step stays green.
* πͺ’ fix: Propagate skillPrimedIdsByName into handoff agent tool context
Handoff agents go through the same `initializeAgent` flow as the primary
(with `listAlwaysApplySkills` now plumbed), so they resolve their own
`manualSkillPrimes` and `alwaysApplySkillPrimes` β but the
`agentToolContexts.set(...)` for handoff agents didn't carry
`skillPrimedIdsByName` into the per-agent context.
That meant `handleReadFileCall` fell back to the full ACL set + a
`prefer*` flag for handoff agents: same-name collisions could resolve to
a different doc than the one whose body got primed, and a
`disable-model-invocation: true` skill primed via manual `$` or
always-apply inside the handoff flow would be blocked from reading its
own bundled files.
Build the map via `buildSkillPrimedIdsByName(config.manualSkillPrimes,
config.alwaysApplySkillPrimes)` for every handoff tool context so
`read_file` behaves identically across primary and handoff agents.
0 commit comments