mirror of
https://github.com/lobehub/lobehub.git
synced 2026-09-20 04:56:13 +08:00
dbc1e47c1b
* 🐛 fix(skill-store): route LobeHub skill URLs to importFromMarket instead of the crawler A `lobehub.com/skills/{identifier}/skill.md` link in a user message was reaching the model with no turn-local guidance. The only rule covering the case lived in the activator's system role, far from the current turn — and the page, once crawled, carries its own "always use the CLI, do NOT make raw API requests" instructions plus a full `npx @lobehub/market-cli register && skills install` walkthrough. Fresher and more specific won: the model crawled the page and went to the sandbox, when the identifier it needed was already in the URL path. - Add `SkillImportRouteInjector`: extracts the marketplace identifier from any LobeHub skill URL in the current user message and appends an explicit instruction to import it via `lobe-skill-store`, stating that it takes precedence over install steps found on the linked page. Gated on the Skill Store being enabled or listed as available. - `SkillStoreManifest.meta.description` now carries the URL → `importFromMarket` rule. Before activation this single line is the only thing the model sees about the tool — its `systemRole` is not injected yet — so a keyword list alone left it outweighed by every already-active tool. - Skill Store `systemRole`: install from the URL you were given rather than browsing to it first; never run marketplace CLI commands; never conclude a skill is missing from an empty `searchSkill` (matching is textual, so "pptx presentation slides" misses a skill named "pptx"); keep queries short. - Activator `systemRole`: generic-tool fallback now requires that no skill URL or identifier was given, instead of firing whenever a search came back empty; ban marketplace CLI commands; `<available_tools>` is described as injected system context rather than a section of the system prompt, which is where it actually lands (`ToolDiscoveryProvider` extends `BaseFirstUserContentProvider`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZvgLyect6csrBufui73ZE * ♻️ refactor(skill-store): make the CLI a ranked last resort, not a flat ban The first pass banned the marketplace CLI outright. That overshoots: the CLI is the legitimate path for an agent that has no Skill Store tool, and an absolute "never" would strand it. The real defect was ordering, not the CLI itself. Replace the prohibition with an explicit ladder — importFromMarket → importSkill → CLI — stated in all four places the model can read it (the turn-local injection, the pre-activation manifest description, and both system roles), with the reason the CLI sits last: it needs a device registration the tools don't, is rate-limited 5 per 30 minutes per IP, and needs a working sandbox. Falling through to it now requires having tried the steps above and saying what failed. Also keeps the rebuttal to the phrase that caused the original detour — "install it as documented" does not promote the CLI, because a skill page documents the CLI for agents that lack this tool. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZvgLyect6csrBufui73ZE * ✨ feat(skill-store): route any skill source to importSkill, not just marketplace URLs The route only recognised `lobehub.com/skills/{identifier}`, which left out most of what `importSkill` actually accepts — a SKILL.md on any host, a GitHub repo or subdirectory, a ZIP package. Those URLs fell straight back to the crawler, which is the failure this route exists to prevent. Classify every URL in the message instead of matching one host: - `lobehub.com/skills/{identifier}` → `importFromMarket` with the identifier - a `SKILL.md` on any host, or a GitHub path with a `skills/` segment → `importSkill` type `url`; both are unambiguous, so no further signal is needed - an ordinary GitHub repo or a `.zip` → `importSkill`, but only when the message also expresses install intent (en + zh). A repo link in a message about reviewing code must not be read as "install this skill" Each detected entry now carries the resolved call, so the injected block names `importFromMarket` with its identifier or `importSkill` with its url and type rather than describing one shape. `metadata.skillImportRoute` reports urls, since an identifier no longer exists for every route. `lobehub.com/skills/skill.md` has no identifier to extract and now falls through to `importSkill` rather than being dropped — it is still a SKILL.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZvgLyect6csrBufui73ZE * 🔒 fix(skill-store): drop trailing URL punctuation without a quadratic regex CodeQL flagged `/[!,.:;?、。,:;?]+$/` as polynomial ReDoS on uncontrolled data, and it is reachable. The pattern has an end anchor but no start anchor, so the engine retries from every offset; on a URL ending in a long punctuation run followed by one more character it does O(n^2) work. `!` and `.` are both inside `URL_PATTERN`'s character class, so `https://example.com/` + 200k `!` + `a` survives URL matching as a single token and reaches the replace — straight from message text. Replace it with a backward scan over a Set. Same result, linear, no backtracking. The regression test uses that 200k input and asserts it completes well inside a second; the previous implementation would not have finished. Reviewed the file's other patterns while here — all either start-anchored against a single URL or a linear scan, and the one nested quantifier (`(?:[\w-]+\.)*` in the marketplace pattern) has a forced split because `[\w-]` cannot match the `.` that terminates each repetition. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZvgLyect6csrBufui73ZE --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>