Skip to content

skill resolution (ts): resolve send_message.skill server-side (#338 parity) - #357

Merged
brentrager merged 1 commit into
mainfrom
th-ebe27d-skill-ts
Aug 14, 2026
Merged

skill resolution (ts): resolve send_message.skill server-side (#338 parity)#357
brentrager merged 1 commit into
mainfrom
th-ebe27d-skill-ts

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Second language in the pearl th-ebe27d fan-out, after .NET (#352).

Problem

The TS server carried send_message.skill on the wire and dropped it. Its own 1.39.0 changelog says so: "Rust is the reference implementation; the TS / Python / Go / .NET servers ignore the field for now."

feature rust dotnet ts python go
file transfer
skill resolution #352 ← this PR

Solution

skills.tsisValidSkillName, stripFrontmatter, skillSection, resolveSection; the SkillResolver host seam via serve({ skillResolver }); DirSkillResolver over SMOOTH_SKILLS_DIR with explicit-wins-then-env, mirroring install_skill_resolver_from_env.

Two behaviors worth calling out:

  • Fail-closed, and resolved before the 202 ack. An unresolvable skill returns SKILL_NOT_FOUND and the turn never runs. Doing it before the ack means a client never sees "accepted" for a turn that was never going to happen — matching where Rust parses it.
  • The body goes to the system prompt, appended last. The persisted user message stays exactly what the user typed, so skill prose never accumulates in history and gets replayed on every later turn — the whole point of moving resolution server-side.

Name validation makes traversal unrepresentable rather than filtered, so ../../etc/passwd can never reach a join().

Verification

All five Rust skills.rs tests ported under their Rust names, plus over-the-socket coverage: fail-closed (asserting chat.calls.length === 0, i.e. the model is never reached), system-prompt-not-user-message placement (including that frontmatter never reaches the model), and blank-as-absent with a resolver installed — so it proves blank means absent rather than merely unreachable.

PASS (254) FAIL (0)     # 245 baseline + 9 new

I checked the baseline on clean main in the same worktree (245/0) before trusting any of this — worth it, because an early version of this branch broke 55 tests and I would otherwise have blamed the harness.

Note on the changeset

Names both @smooai/smooth-operator-server and the lockstep anchor @smooai/smooth-operator, per #346. Omitting the anchor is precisely what stranded the .NET work in #348/#352 unpublished for two days. The guard added in #356 enforces this for stamped trees; typescript/ isn't one, so this is convention rather than enforcement — but shipping the TS server out of lockstep with the rest is not something to start doing.

🤖 Generated with Claude Code

https://claude.ai/code/session_012iM1Q8JC1H83H2FXQVQNs9

…side, Rust #338 parity

Second language in the fan-out after .NET (#352). The TS server had the field on
the wire and ignored it, exactly as its own changelog admitted.

- skills.ts: isValidSkillName / stripFrontmatter / skillSection / resolveSection
- SkillResolver seam via serve({ skillResolver }); DirSkillResolver over
  SMOOTH_SKILLS_DIR with explicit-wins-then-env, mirroring Rust's
  install_skill_resolver_from_env
- Fail-CLOSED, and resolved BEFORE the 202 ack so a client never gets
  "accepted" for a turn that will never run
- Body appended LAST to the system prompt; the persisted user message stays
  exactly what the user typed

Name validation makes traversal unrepresentable rather than filtered.

Changeset names BOTH the TS package and the lockstep anchor, per #346 — the
omission of the anchor is what stranded the .NET work in #348/#352.

Tests: five Rust skills.rs tests ported under their Rust names + over-the-socket
fail-closed / placement / blank-as-absent. 254 green (245 baseline + 9).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012iM1Q8JC1H83H2FXQVQNs9
@changeset-bot

changeset-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 10647b5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@smooai/smooth-operator-server Minor
@smooai/smooth-operator Minor
@smooai/smooth-operator-web-chat-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@brentrager
brentrager merged commit caa3678 into main Aug 14, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant