@skilder-ai/runtime 0.9.16 → 0.9.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -87716,10 +87716,14 @@ var OPTIMIZATION_INSIGHTS_TIMEFRAMES = [
87716
87716
  ];
87717
87717
  var DEFAULT_OPTIMIZATION_INSIGHTS_LOOKBACK_SECONDS = 30 * 24 * 60 * 60;
87718
87718
 
87719
- // ../common/src/constants.node.ts
87720
- var MCP_CALL_TOOL_TIMEOUT = parseInt(process.env.MCP_CALL_TOOL_TIMEOUT ?? "60000", 10);
87721
- var DEFAULT_REQUEST_TIMEOUT = parseInt(process.env.DEFAULT_REQUEST_TIMEOUT ?? "10000", 10);
87722
- var SCRIPT_ROUTE_TIMEOUT_MS = 31e4;
87719
+ // ../common/src/node-helpers/env.ts
87720
+ function parseStringEnv(name21, fallback) {
87721
+ const raw = process.env[name21];
87722
+ return raw === void 0 || raw === "" ? fallback : raw;
87723
+ }
87724
+ function parseUrlEnv(name21, fallback) {
87725
+ return parseStringEnv(name21, fallback).replace(/\/+$/, "");
87726
+ }
87723
87727
  function parsePositiveIntEnv(name21, fallback) {
87724
87728
  const raw = process.env[name21];
87725
87729
  if (raw === void 0 || raw === "") return fallback;
@@ -87729,6 +87733,11 @@ function parsePositiveIntEnv(name21, fallback) {
87729
87733
  }
87730
87734
  return parsed;
87731
87735
  }
87736
+
87737
+ // ../common/src/constants.node.ts
87738
+ var MCP_CALL_TOOL_TIMEOUT = parseInt(process.env.MCP_CALL_TOOL_TIMEOUT ?? "60000", 10);
87739
+ var DEFAULT_REQUEST_TIMEOUT = parseInt(process.env.DEFAULT_REQUEST_TIMEOUT ?? "10000", 10);
87740
+ var SCRIPT_ROUTE_TIMEOUT_MS = 31e4;
87732
87741
  var DCR_REFRESH_STARTUP_DELAY_MS = parsePositiveIntEnv("DCR_REFRESH_STARTUP_DELAY_MS", 3e4);
87733
87742
  var DCR_REFRESH_INTERVAL_MS = parsePositiveIntEnv("DCR_REFRESH_INTERVAL_MS", 60 * 60 * 1e3);
87734
87743
  var DCR_REFRESH_STALE_AFTER_MS = parsePositiveIntEnv("DCR_REFRESH_STALE_AFTER_MS", 6 * 60 * 60 * 1e3);
@@ -87739,6 +87748,41 @@ var DCR_REFRESH_MAX_FAILURE_WINDOW_MS = parsePositiveIntEnv(
87739
87748
  72 * 60 * 60 * 1e3
87740
87749
  );
87741
87750
 
87751
+ // ../common/src/provisioning.constants.ts
87752
+ var SKILDER_HAT_DEFAULTS = {
87753
+ slug: "skilder",
87754
+ name: "Skilder",
87755
+ /** HatColor enum value (string-compatible). */
87756
+ color: "BLUE",
87757
+ description: "A gentle and smart assistant that knows the Skilder platform inside and out. Helps users create and manage skills, hats, tools, and automations.",
87758
+ instructions: `You are the Skilder Assistant \u2014 a gentle, knowledgeable, and proactive AI helper.
87759
+
87760
+ Your role is to help users get the most out of the Skilder platform. You know every feature, concept, and workflow: hats, skills, MCP servers, tools, runtimes, organizational hierarchy, and the Studio conversation interface.
87761
+
87762
+ Behavior guidelines:
87763
+ - Be welcoming and patient, especially with new users
87764
+ - Explain concepts in plain language before using technical terms
87765
+ - Proactively suggest next steps and quick wins
87766
+ - When asked to build something, use your Skilder tools to do it \u2014 don't just explain how
87767
+ - If you're unsure about the user's intent, ask a clarifying question
87768
+ - Keep responses concise but complete
87769
+
87770
+ You have access to Skilder MCP tools for managing skills, hats, org units, and tool configurations. Use them to take action on behalf of the user when appropriate.
87771
+
87772
+ ## Tool honesty (critical)
87773
+
87774
+ - ONLY call tools that are explicitly listed under the "Available Tools" of a skill you have learned. Call them by their exact name.
87775
+ - NEVER invent or guess a tool name, and NEVER invent a script (e.g. a "create_hat.py") that wasn't listed. If a plausible tool isn't in your learned skills, it does not exist for you.
87776
+ - NEVER claim an action succeeded unless the tool actually returned a successful result. If a tool call errors or is unavailable, say so plainly \u2014 do not pretend it worked.
87777
+ - If you need a capability you don't have a tool for, STOP and tell the user the skill is missing that tool, then suggest they either (i) browse the **Library** to add an existing tool/skill, or (ii) configure a new tool/MCP server. Do not work around a missing tool by faking it.`
87778
+ };
87779
+ var SKILDER_SKILL_IDS = [
87780
+ "skilder-skill-architect",
87781
+ "skilder-platform-expert",
87782
+ "skilder-hat-designer",
87783
+ "human-discovery"
87784
+ ];
87785
+
87742
87786
  // ../../node_modules/.pnpm/bcryptjs@3.0.3/node_modules/bcryptjs/index.js
87743
87787
  var nextTick = typeof setImmediate === "function" ? setImmediate : typeof scheduler === "object" && typeof scheduler.postTask === "function" ? scheduler.postTask.bind(scheduler) : setTimeout;
87744
87788
  var BASE64_CODE = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split("");
@@ -110765,6 +110809,1121 @@ function buildStreamTransport(transport) {
110765
110809
  var CONNECTED_WINDOW_HOURS = 24;
110766
110810
  var CONNECTED_WINDOW_MS = CONNECTED_WINDOW_HOURS * 60 * 60 * 1e3;
110767
110811
 
110812
+ // ../common/src/skill-catalog-meta.ts
110813
+ var SKILDER_META_FEATURED_SKILLS = [
110814
+ {
110815
+ slug: "skilder-skill-architect",
110816
+ name: "Skilder Skill Architect",
110817
+ description: "Craft high-quality Skilder skills: instructions writing, and attaching references/scripts",
110818
+ tags: ["skilder", "meta", "skill-building"],
110819
+ instructions: `Craft high-quality Skilder skills using these principles.
110820
+
110821
+ For multi-step workflows, use the **delegate** static tool which spawns a sub-agent with full Skilder MCP access.
110822
+
110823
+ ## Writing Effective Instructions
110824
+
110825
+ - Keep instructions **concise** \u2014 they are revealed on \`learn\` as a quick guide.
110826
+ - Use **progressive disclosure**: link deep-dive content as named reference documents.
110827
+ - Write in **imperative voice**: "Query the database using..." not "The database can be queried..."
110828
+ - Include **concrete examples** for non-obvious parameters or workflows.
110829
+
110830
+ See instructions-guide.md for length guidelines, templates, and common mistakes.
110831
+
110832
+ ## Attaching References & Scripts
110833
+
110834
+ - Attach named documents (references) for deep-dive content agents can fetch on demand.
110835
+ - Attach executable scripts for automation the agent can run via \`execute_script\`.
110836
+ - Agents will see learning paths and execution paths automatically in the learn response.
110837
+
110838
+ See related-resources.md for naming conventions and content guidelines.`,
110839
+ related: [
110840
+ {
110841
+ name: "instructions-guide.md",
110842
+ description: "Writing instructions: tone, length, structure, and common mistakes",
110843
+ mimetype: "text/markdown",
110844
+ content: `# Writing Effective Skill Instructions
110845
+
110846
+ ## Principles
110847
+
110848
+ **Be concise.** Instructions are shown on \`learn\`. Keep them scannable \u2014 agents read to act, not to study.
110849
+
110850
+ **Imperative voice.** Write "Query the database using the filter parameter" not "The database can be queried using..."
110851
+
110852
+ **Progressive disclosure.** Put deep detail in \`@references/...\` documents.
110853
+ - Instructions = orientation (what this skill does and how to start)
110854
+ - References = manual (edge cases, full field lists, error codes)
110855
+
110856
+ **Give examples.** When a parameter or workflow is non-obvious, include a one-liner example inline.
110857
+
110858
+ ---
110859
+
110860
+ ## Recommended Structure
110861
+
110862
+ \`\`\`markdown
110863
+ Brief description of what this skill does and when to use it.
110864
+
110865
+ ## Core Concepts
110866
+ - Key term 1: brief definition
110867
+ - Key term 2: brief definition
110868
+
110869
+ ## How To Use
110870
+ 1. First step with example
110871
+ 2. Second step
110872
+ 3. Third step
110873
+
110874
+ ## Important Constraints
110875
+ - Limitation or gotcha 1
110876
+ - Limitation or gotcha 2
110877
+
110878
+ For deeper guidance, see:
110879
+ - @references/doc-name.md \u2014 what it covers
110880
+ \`\`\`
110881
+
110882
+ ---
110883
+
110884
+ ## Length Guidelines
110885
+
110886
+ | Skill complexity | Instructions length |
110887
+ |-------------------------|---------------------|
110888
+ | Simple (1\u20133 tools) | 50\u2013150 words |
110889
+ | Moderate (4\u20138 tools) | 150\u2013300 words |
110890
+ | Complex (workflow) | 200\u2013400 words |
110891
+
110892
+ ---
110893
+
110894
+ ## Common Mistakes
110895
+
110896
+ | Mistake | Fix |
110897
+ |---|---|
110898
+ | Listing all tool parameters inline | Move to a reference document |
110899
+ | "Use the tools as needed" (too vague) | Give concrete step-by-step guidance |
110900
+ | No examples for ambiguous inputs | Add a one-liner example per non-obvious param |
110901
+ | Missing constraints | State rate limits, permissions, error conditions |
110902
+ | Instructions longer than 500 words | Split into references |
110903
+ `
110904
+ },
110905
+ {
110906
+ name: "related-resources.md",
110907
+ description: "How to attach, name, and document references and scripts",
110908
+ mimetype: "text/markdown",
110909
+ content: `# Attaching References and Scripts
110910
+
110911
+ ## References
110912
+
110913
+ References are named markdown documents attached to a skill. They appear in the learn response as paths the agent can fetch on demand.
110914
+
110915
+ ### Creating a Reference
110916
+
110917
+ In the Studio UI: **Skill \u2192 References tab \u2192 Add Reference**
110918
+ Via Skilder MCP server: \`create_skill_reference\` tool
110919
+
110920
+ ### Naming Conventions
110921
+
110922
+ Use descriptive, hyphenated names with \`.md\` extension:
110923
+ - \`api-guide.md\` \u2014 REST endpoint documentation
110924
+ - \`field-mapping.md\` \u2014 Input/output field mappings
110925
+ - \`error-codes.md\` \u2014 Error code reference
110926
+ - \`examples.md\` \u2014 Request/response examples
110927
+
110928
+ ### Mentioning in Instructions
110929
+
110930
+ Use \`@references/filename.md\` anywhere in the instructions text. The learn response automatically generates the learning path:
110931
+
110932
+ \`\`\`
110933
+ learn --path "/Skill Name/references/filename.md"
110934
+ \`\`\`
110935
+
110936
+ ### Content Guidelines
110937
+
110938
+ - References should be **self-contained** documents
110939
+ - Use headers, tables, and code blocks freely
110940
+ - Include examples, not just definitions
110941
+ - References can be updated independently of skill instructions
110942
+
110943
+ ---
110944
+
110945
+ ## Scripts
110946
+
110947
+ Scripts are executable code attached to a skill. They run on the linked edge runtime.
110948
+
110949
+ ### Supported Types
110950
+
110951
+ - **Python** \u2014 \`.py\` files
110952
+ - **Bash** \u2014 \`.sh\` files (on compatible runtimes)
110953
+
110954
+ ### Mentioning in Instructions
110955
+
110956
+ Use \`@scripts/filename.py\` in instructions. The learn response shows the execution path:
110957
+
110958
+ \`\`\`
110959
+ execute_script --path "/Skill Name/scripts/filename.py --arg=value"
110960
+ \`\`\`
110961
+
110962
+ ### Best Practices
110963
+
110964
+ - Add a \`--help\` argument to scripts for self-documentation
110965
+ - Use descriptive argument names
110966
+ - Keep scripts focused \u2014 one script per distinct operation
110967
+ - Document expected inputs and outputs in the script header comment
110968
+ `
110969
+ }
110970
+ ]
110971
+ },
110972
+ {
110973
+ slug: "skilder-platform-expert",
110974
+ name: "Skilder Platform Expert",
110975
+ description: "Full conceptual guide: entities, relationships, constraints, and common workflows for managing a Skilder workspace",
110976
+ tags: ["skilder", "meta", "platform"],
110977
+ instructions: `Master the Skilder platform entities and their relationships.
110978
+
110979
+ ## Core Entities
110980
+
110981
+ - **Workspace** \u2014 Tenant boundary. All resources (hats, skills, runtimes) are workspace-scoped.
110982
+ - **Hat** \u2014 A named, color-coded collection of skills. Represents a role or persona for an agent.
110983
+ - **Skill** \u2014 A unit of capability: instructions + MCP tools + references + scripts.
110984
+ - **MCP Tool** \u2014 A callable tool exposed by an external MCP server, linked to a skill.
110985
+ - **Reference** \u2014 A named markdown document attached to a skill for contextual knowledge.
110986
+ - **Script** \u2014 Executable code (Python, Bash) attached to a skill, run via \`execute_script\`.
110987
+ - **Runtime** \u2014 An edge runtime instance that connects external MCP servers to Skilder.
110988
+
110989
+ ## Key Relationships
110990
+
110991
+ \`\`\`
110992
+ Workspace
110993
+ \u2514\u2500\u2500 Hats \u2192 Skills \u2192 MCP Tools (served by Runtimes)
110994
+ \u2514\u2500\u2500 References (markdown knowledge)
110995
+ \u2514\u2500\u2500 Scripts (executable automation)
110996
+ \`\`\`
110997
+
110998
+ ## Common Workflows
110999
+
111000
+ 1. **Add a skill**: Create skill \u2192 attach MCP tools from a runtime \u2192 add references \u2192 assign to a hat
111001
+ 2. **Agent connects**: \`init_skilder\` \u2192 \`learn /hats/Hat Name\` or \`learn /Skill Name\` \u2192 use unlocked tools
111002
+ 3. **Import from GitHub**: Use the GitHub import to pull SKILL.md + references from a repo
111003
+
111004
+ For deeper guidance, see:
111005
+ - entities.md \u2014 full entity reference with all fields and constraints
111006
+ - workflows.md \u2014 step-by-step common workflows with the Skilder MCP server
111007
+ - mcp-tools.md \u2014 how MCP tools are registered, routed, and exposed to agents`,
111008
+ related: [
111009
+ {
111010
+ name: "entities.md",
111011
+ description: "Full entity reference with all fields and constraints",
111012
+ mimetype: "text/markdown",
111013
+ content: `# Skilder Entity Reference
111014
+
111015
+ ## Workspace
111016
+
111017
+ The top-level organizational boundary. Every resource belongs to exactly one workspace.
111018
+
111019
+ | Field | Type | Notes |
111020
+ |---|---|---|
111021
+ | id | ID | Immutable |
111022
+ | name | String | Display name |
111023
+ | slug | String | URL-safe identifier |
111024
+ | members | [User] | Workspace members with roles |
111025
+
111026
+ **Constraints:** Skills, hats, and runtimes cannot be shared across workspaces.
111027
+
111028
+ ---
111029
+
111030
+ ## Hat
111031
+
111032
+ A named, color-coded collection of skills representing a role.
111033
+
111034
+ | Field | Type | Notes |
111035
+ |---|---|---|
111036
+ | id | ID | Immutable |
111037
+ | name | String | Role name shown to agents |
111038
+ | description | String | Brief role description (shown in init_skilder) |
111039
+ | instructions | String | Full hat-level guidance revealed on learn |
111040
+ | color | String | Hex color (e.g., \`#3B82F6\`) |
111041
+ | skills | [Skill] | Ordered list of skills in this hat |
111042
+ | disabled | Boolean | If true, hidden from agents |
111043
+
111044
+ **Constraints:** A skill can belong to multiple hats. A hat must have at least one enabled skill to appear in \`init_skilder\`.
111045
+
111046
+ ---
111047
+
111048
+ ## Skill
111049
+
111050
+ A unit of capability: instructions, tools, references, and scripts.
111051
+
111052
+ | Field | Type | Notes |
111053
+ |---|---|---|
111054
+ | id | ID | Immutable |
111055
+ | name | String | Used as the learn path segment (e.g., \`learn "/My Skill"\`) |
111056
+ | description | String | \u2264 500 chars; shown before the agent learns the skill |
111057
+ | instructions | String | Full instructions revealed on \`learn\` |
111058
+ | mcpTools | [MCPTool] | Tools exposed when skill is learned |
111059
+ | references | [SkillReference] | Named knowledge documents |
111060
+ | scripts | [SkillScript] | Executable scripts |
111061
+ | disabled | Boolean | If true, hidden from agents and the catalog |
111062
+
111063
+ **Constraints:** Skill names must be unique within a workspace. Description is capped at 500 characters.
111064
+
111065
+ ---
111066
+
111067
+ ## MCP Tool
111068
+
111069
+ An individual callable tool from an external MCP server.
111070
+
111071
+ | Field | Type | Notes |
111072
+ |---|---|---|
111073
+ | id | ID | Immutable |
111074
+ | name | String | Tool name as registered in the MCP server |
111075
+ | description | String | Shown in the agent's tool list |
111076
+ | inputSchema | JSON | JSON Schema for tool arguments |
111077
+ | mcpServer | MCPServer | The server that provides this tool |
111078
+
111079
+ **Constraints:** Tool names must be globally unique within a workspace (they are merged into a single tool list per agent session).
111080
+
111081
+ ---
111082
+
111083
+ ## Skill Reference
111084
+
111085
+ A named markdown document attached to a skill.
111086
+
111087
+ | Field | Type | Notes |
111088
+ |---|---|---|
111089
+ | id | ID | Immutable |
111090
+ | name | String | Filename used in the learn path (e.g., \`api-guide.md\`) |
111091
+ | description | String | Brief description shown in the References section |
111092
+ | instructions | String | Full markdown content of the document |
111093
+
111094
+ **Access path:** \`learn "/Skill Name/references/filename.md"\`
111095
+
111096
+ ---
111097
+
111098
+ ## Skill Script
111099
+
111100
+ Executable code attached to a skill.
111101
+
111102
+ | Field | Type | Notes |
111103
+ |---|---|---|
111104
+ | id | ID | Immutable |
111105
+ | name | String | Filename used in the execute path (e.g., \`transform.py\`) |
111106
+ | type | ScriptType | PYTHON | BASH |
111107
+ | code | String | Script source code |
111108
+ | runtime | Runtime | The edge runtime that will execute the script |
111109
+
111110
+ **Execution path:** \`execute_script "/Skill Name/scripts/filename.py --arg=value"\`
111111
+
111112
+ ---
111113
+
111114
+ ## Runtime
111115
+
111116
+ An edge runtime instance that connects external MCP servers to Skilder.
111117
+
111118
+ | Field | Type | Notes |
111119
+ |---|---|---|
111120
+ | id | ID | Immutable |
111121
+ | name | String | Display name |
111122
+ | runtimeId | String | Unique runtime identifier for NATS routing |
111123
+ | mcpServers | [MCPServer] | MCP servers managed by this runtime |
111124
+
111125
+ **Notes:** Scripts are linked to a specific runtime. MCP tools are linked to an MCP server, which belongs to a runtime.
111126
+ `
111127
+ },
111128
+ {
111129
+ name: "workflows.md",
111130
+ description: "Step-by-step common workflows with the Skilder MCP server",
111131
+ mimetype: "text/markdown",
111132
+ content: `# Common Skilder Workflows
111133
+
111134
+ These workflows use the Skilder MCP server tools available after connecting.
111135
+
111136
+ ---
111137
+
111138
+ ## Create a Skill
111139
+
111140
+ 1. **Create the skill** using \`create_skill\` with name and description.
111141
+ 2. **Add instructions** via \`update_skill\` \u2014 keep them concise (see Skilder Skill Architect skill).
111142
+ 3. **Attach MCP tools** using \`add_mcp_tool_to_skill\` \u2014 tools must already exist on a runtime.
111143
+ 4. **Add references** using \`create_skill_reference\` for each knowledge document.
111144
+ 5. **Assign to a hat** using \`add_skill_to_hat\` \u2014 or create a new hat first.
111145
+
111146
+ ---
111147
+
111148
+ ## Create a Hat
111149
+
111150
+ 1. **Create the hat** using \`create_hat\` with name, description, and color.
111151
+ 2. **Add skills** using \`add_skill_to_hat\` \u2014 skills must already exist.
111152
+ 3. **Set hat instructions** (optional) via \`update_hat\` for hat-level agent guidance.
111153
+
111154
+ ---
111155
+
111156
+ ## Import a Skill from GitHub
111157
+
111158
+ Use \`import_skill_from_github\` with the repository URL and path to the SKILL.md file.
111159
+
111160
+ The import reads:
111161
+ - \`SKILL.md\` \u2014 skill metadata (name, description) and instructions
111162
+ - \`references/\` directory \u2014 all \`.md\` files become skill references
111163
+ - \`scripts/\` directory \u2014 all script files become skill scripts (linked to a specified runtime)
111164
+
111165
+ ---
111166
+
111167
+ ## Update a Skill's Instructions
111168
+
111169
+ Use \`update_skill\` with the \`instructions\` field. Changes take effect on the next agent session (the catalog is rebuilt on each user handshake).
111170
+
111171
+ ---
111172
+
111173
+ ## Disable a Skill or Hat
111174
+
111175
+ Use \`update_skill\` or \`update_hat\` with \`disabled: true\`. The resource is hidden from agents immediately (the catalog is refreshed on next connection).
111176
+
111177
+ ---
111178
+
111179
+ ## Agent Session Lifecycle
111180
+
111181
+ 1. Agent connects to the Skilder runtime (via MCP server URL).
111182
+ 2. Runtime receives the workspace catalog from the backend via NATS.
111183
+ 3. Agent calls \`init_skilder\` \u2014 sees available hats and skills (name + description only).
111184
+ 4. Agent calls \`learn "/hats/Hat Name"\` or \`learn "/Skill Name"\` \u2014 receives full instructions + tool list.
111185
+ 5. Newly unlocked MCP tools appear in the agent's tool list immediately.
111186
+ 6. Agent calls \`execute_script\` to run attached scripts on the edge runtime.
111187
+ `
111188
+ },
111189
+ {
111190
+ name: "mcp-tools.md",
111191
+ description: "How MCP tools are registered, routed, and exposed to agents",
111192
+ mimetype: "text/markdown",
111193
+ content: `# MCP Tool Registration and Routing
111194
+
111195
+ ## Overview
111196
+
111197
+ MCP tools in Skilder are not defined directly \u2014 they are **discovered** from external MCP servers running on edge runtimes. The flow is:
111198
+
111199
+ \`\`\`
111200
+ External MCP Server \u2192 Runtime (discovers tools) \u2192 Skilder (stores MCPTool nodes) \u2192 Skill (attaches tools) \u2192 Agent (uses tools)
111201
+ \`\`\`
111202
+
111203
+ ---
111204
+
111205
+ ## Registration
111206
+
111207
+ When an edge runtime starts, it:
111208
+ 1. Spawns or connects to the external MCP servers configured for that runtime.
111209
+ 2. Calls \`tools/list\` on each MCP server.
111210
+ 3. Registers the discovered tools as \`MCPTool\` nodes in Skilder (linked to the runtime's MCP server).
111211
+
111212
+ Tools are re-registered on runtime restart. If a tool's input schema changes, the stored node is updated.
111213
+
111214
+ ---
111215
+
111216
+ ## Routing
111217
+
111218
+ When an agent calls an MCP tool:
111219
+
111220
+ 1. The user runtime looks up the tool's routing info (\`skillId\`, \`runtimeId\`).
111221
+ 2. It publishes a \`SkillCallToolRequest\` to NATS subject: \`{workspaceId}.call-tool.{toolId}.{runtimeId}\`
111222
+ 3. The target edge runtime receives the request and forwards it to the appropriate MCP server.
111223
+ 4. The response is returned via NATS and forwarded to the agent.
111224
+
111225
+ **Timeout:** Default tool call timeout is configurable; long-running operations should use scripts instead.
111226
+
111227
+ ---
111228
+
111229
+ ## Tool Name Uniqueness
111230
+
111231
+ Tool names must be **globally unique** within a workspace. If two MCP servers provide a tool with the same name, only one can be linked to a skill. Use the MCP server's tool name prefix feature to disambiguate.
111232
+
111233
+ ---
111234
+
111235
+ ## Progressive Exposure
111236
+
111237
+ Tools are not visible to the agent until the skill they belong to is learned. This keeps the agent's tool list focused on what is relevant for the current task.
111238
+
111239
+ - After \`init_skilder\`: only static tools are visible (\`init_skilder\`, \`learn\`, \`execute_script\`, \`feedback_skill\`).
111240
+ - After \`learn "/Skill Name"\`: all tools linked to that skill are added to the tool list.
111241
+ - After \`learn "/hats/Hat Name"\`: all tools from all skills in that hat are added.
111242
+ `
111243
+ }
111244
+ ]
111245
+ },
111246
+ {
111247
+ slug: "skilder-hat-designer",
111248
+ name: "Skilder Hat Designer",
111249
+ description: "Design role-based hat collections: org unit mapping, skill selection, and color conventions",
111250
+ tags: ["skilder", "meta", "hats"],
111251
+ instructions: `Design effective hat collections that give agents focused, role-appropriate context.
111252
+
111253
+ ## What Is a Hat?
111254
+
111255
+ A hat is a named role a workspace assigns to an agent. It bundles a set of skills the agent needs for that role, using a color for quick visual identification in the Studio UI.
111256
+
111257
+ ## Design Principles
111258
+
111259
+ - **One hat per role** \u2014 Align hats with job functions (e.g., "Customer Support Specialist", "Finance Analyst"), not tasks.
111260
+ - **Minimal skill sets** \u2014 Include only skills the role actually needs. Smaller hats mean faster, more focused agents.
111261
+ - **Clear names** \u2014 Use role names agents can recognize: "Senior DevOps Engineer" not "Hat 1".
111262
+ - **Hat-level instructions** \u2014 Use the hat's \`instructions\` field for role-level context that applies to all skills in the hat.
111263
+
111264
+ ## Org Unit Mapping
111265
+
111266
+ Map hats to organizational units (teams, departments, functions):
111267
+ - A hat can be shared across multiple org units if roles overlap.
111268
+ - Create separate hats for roles with meaningfully different skill sets, even within the same team.
111269
+ - Avoid creating redundant hats for minor variations \u2014 use skill \`instructions\` differences instead.
111270
+
111271
+ ## Color Conventions
111272
+
111273
+ Colors should reflect the function or urgency of the role. See color-palette.md for exact hex codes.
111274
+
111275
+ | Role type | Color family | Example |
111276
+ |---|---|---|
111277
+ | Technical / Engineering | Blue | \`#3B82F6\` |
111278
+ | Operations / Support | Green | \`#10B981\` |
111279
+ | Customer-facing / Sales | Orange | \`#F59E0B\` |
111280
+ | Admin / Governance | Red | \`#EF4444\` |
111281
+ | Creative / Strategy | Purple | \`#8B5CF6\` |
111282
+ | Data / Analytics | Teal | \`#14B8A6\` |
111283
+
111284
+ For a complete hat design guide with examples and anti-patterns, see hat-design-guide.md.`,
111285
+ related: [
111286
+ {
111287
+ name: "hat-design-guide.md",
111288
+ description: "Complete hat design guide with examples, anti-patterns, and org mapping templates",
111289
+ mimetype: "text/markdown",
111290
+ content: `# Hat Design Guide
111291
+
111292
+ ## Anatomy of a Well-Designed Hat
111293
+
111294
+ A good hat has:
111295
+ - **A focused role name** that an agent can understand without ambiguity
111296
+ - **A concise description** (1\u20132 sentences) explaining the role's purpose
111297
+ - **Hat-level instructions** for context that applies across all skills in the hat (e.g., tone, constraints, escalation rules)
111298
+ - **3\u20138 skills** \u2014 enough to cover the role's needs without overwhelming the agent
111299
+ - **A consistent color** that matches the role's function
111300
+
111301
+ ---
111302
+
111303
+ ## Examples
111304
+
111305
+ ### Good Hat Design: "Customer Support Tier 1"
111306
+
111307
+ \`\`\`
111308
+ Name: Customer Support Tier 1
111309
+ Description: Handle inbound customer inquiries, ticket triage, and FAQ resolution
111310
+ Color: #F59E0B (orange \u2014 customer-facing)
111311
+ Skills:
111312
+ - Ticket Management (create, update, close tickets)
111313
+ - Knowledge Base Search (large article set)
111314
+ - Customer Profile Lookup (read-only CRM access)
111315
+ Instructions: "Always greet customers by first name. Escalate to Tier 2 if resolution
111316
+ requires more than 3 tool calls. Log all interactions."
111317
+ \`\`\`
111318
+
111319
+ ### Anti-Pattern: "Everything Hat"
111320
+
111321
+ \`\`\`
111322
+ Name: General Assistant
111323
+ Skills: (20 skills covering billing, HR, DevOps, marketing, legal...)
111324
+ \`\`\`
111325
+
111326
+ **Problem:** The agent sees every tool from every skill after learning the hat. This floods the tool list and dilutes focus.
111327
+
111328
+ **Fix:** Split into role-specific hats. Use \`learn "/Skill Name"\` for ad-hoc access to individual skills.
111329
+
111330
+ ---
111331
+
111332
+ ## Skill Selection Checklist
111333
+
111334
+ For each skill you consider adding to a hat, ask:
111335
+ - [ ] Does this role need this capability in their typical workflow?
111336
+ - [ ] Would an agent in this role be authorized to use these tools?
111337
+ - [ ] Does this skill's instructions align with this role's context?
111338
+ - [ ] Are there fewer than 10 skills in the hat already?
111339
+
111340
+ ---
111341
+
111342
+ ## Org Unit Mapping Template
111343
+
111344
+ | Org Unit | Primary Hat | Secondary Hat (optional) |
111345
+ |---|---|---|
111346
+ | Engineering | "Software Engineer" | "DevOps Operator" |
111347
+ | Finance | "Finance Analyst" | "Finance Admin" |
111348
+ | HR | "HR Business Partner" | \u2014 |
111349
+ | Customer Support | "CS Tier 1" | "CS Tier 2" |
111350
+ | Legal | "Legal Reviewer" | \u2014 |
111351
+
111352
+ **Note:** Agents typically wear one hat per session. Design hats for the most common single-session workflow, not for covering every possible task.
111353
+
111354
+ ---
111355
+
111356
+ ## Hat Instructions Best Practices
111357
+
111358
+ Hat-level \`instructions\` appear when an agent learns the hat (before individual skill instructions). Use them for:
111359
+ - Role identity framing ("You are a Senior Support Specialist...")
111360
+ - Cross-skill constraints ("Always check the customer's entitlement before using billing tools")
111361
+ - Escalation rules ("If unsure, prefer learn over act")
111362
+ - Tone and communication style guidance
111363
+
111364
+ Keep hat instructions under 200 words \u2014 they are shown alongside each skill's instructions, so context adds up quickly.
111365
+ `
111366
+ },
111367
+ {
111368
+ name: "color-palette.md",
111369
+ description: "Exact color codes and usage conventions for each role type",
111370
+ mimetype: "text/markdown",
111371
+ content: `# Hat Color Palette
111372
+
111373
+ Colors are stored as hex strings (e.g., \`#3B82F6\`). Use the role-type conventions below for consistency across your workspace.
111374
+
111375
+ ---
111376
+
111377
+ ## Technical / Engineering
111378
+
111379
+ | Shade | Hex | Usage |
111380
+ |---|---|---|
111381
+ | Blue 500 | \`#3B82F6\` | General engineering roles |
111382
+ | Blue 700 | \`#1D4ED8\` | Senior / lead engineering roles |
111383
+ | Indigo 500 | \`#6366F1\` | Platform / infrastructure roles |
111384
+ | Cyan 500 | \`#06B6D4\` | Backend / API roles |
111385
+
111386
+ ---
111387
+
111388
+ ## Operations / Support
111389
+
111390
+ | Shade | Hex | Usage |
111391
+ |---|---|---|
111392
+ | Green 500 | \`#10B981\` | General operations |
111393
+ | Green 700 | \`#047857\` | Operations leads |
111394
+ | Lime 500 | \`#84CC16\` | IT support |
111395
+ | Emerald 500 | \`#10B981\` | Service reliability |
111396
+
111397
+ ---
111398
+
111399
+ ## Customer-Facing / Sales
111400
+
111401
+ | Shade | Hex | Usage |
111402
+ |---|---|---|
111403
+ | Amber 500 | \`#F59E0B\` | Customer support |
111404
+ | Orange 500 | \`#F97316\` | Sales / Account management |
111405
+ | Yellow 500 | \`#EAB308\` | Marketing |
111406
+
111407
+ ---
111408
+
111409
+ ## Admin / Governance
111410
+
111411
+ | Shade | Hex | Usage |
111412
+ |---|---|---|
111413
+ | Red 500 | \`#EF4444\` | Admin roles |
111414
+ | Rose 500 | \`#F43F5E\` | Compliance / Legal |
111415
+ | Red 700 | \`#B91C1C\` | Security / Audit |
111416
+
111417
+ ---
111418
+
111419
+ ## Creative / Strategy
111420
+
111421
+ | Shade | Hex | Usage |
111422
+ |---|---|---|
111423
+ | Purple 500 | \`#8B5CF6\` | Product / Strategy |
111424
+ | Violet 500 | \`#7C3AED\` | Design / UX |
111425
+ | Fuchsia 500 | \`#D946EF\` | Creative / Content |
111426
+
111427
+ ---
111428
+
111429
+ ## Data / Analytics
111430
+
111431
+ | Shade | Hex | Usage |
111432
+ |---|---|---|
111433
+ | Teal 500 | \`#14B8A6\` | Data analysts |
111434
+ | Sky 500 | \`#0EA5E9\` | Data engineers |
111435
+ | Slate 500 | \`#64748B\` | Reporting / BI |
111436
+
111437
+ ---
111438
+
111439
+ ## Neutral / Cross-Functional
111440
+
111441
+ | Shade | Hex | Usage |
111442
+ |---|---|---|
111443
+ | Gray 500 | \`#6B7280\` | Cross-functional or temporary roles |
111444
+ | Stone 500 | \`#78716C\` | General purpose / sandbox |
111445
+
111446
+ ---
111447
+
111448
+ ## Tips
111449
+
111450
+ - Use **darker shades** for senior/lead variants of a role.
111451
+ - Keep color assignments **consistent** across your workspace \u2014 the same role type should always use the same color family.
111452
+ - Avoid using the same hex for two different hats in the same workspace \u2014 agents and users will confuse them.
111453
+ `
111454
+ }
111455
+ ]
111456
+ }
111457
+ ];
111458
+
111459
+ // ../common/src/skill-catalog-onboarding.ts
111460
+ var ONBOARDING_FEATURED_SKILLS = [
111461
+ {
111462
+ slug: "human-discovery",
111463
+ name: "Human Discovery",
111464
+ description: `Use this skill to help a person discover what AI skill would be most useful for their job. Trigger whenever someone says things like "I don't know where to start with AI", "what skill should I create", "help me find a use case", "I'm not technical but want to use AI", "suggest a skill for my job", or anything that implies they want AI help but don't know how. Walk them through a guided discovery process: understand their job in 4 questions or fewer, suggest 3 pain points addressable by a Claude skill (no heavy integrations), let them pick one, interview them briefly, then write the skill together.`,
111465
+ tags: ["discovery", "onboarding", "first-time-user"],
111466
+ instructions: `# Skill Discovery for first time user \u2014 Find the Right AI Skill for Any Job
111467
+
111468
+ You are helping a non-technical person discover which AI skill would make the biggest difference in their work. Your tone should be **warm, curious, and jargon-free**. Never use terms like "LLM", "prompt", "API", "workflow automation". Speak like a friendly colleague, not a consultant. Apply ALWAYS if the user say he is new, first time using, don't know what to do with AI.
111469
+
111470
+ ---
111471
+
111472
+ ## Phase 1: Discover the Human (max 4 questions)
111473
+
111474
+ Your goal is to build a clear picture of who this person is and where they struggle \u2014 in 4 questions or fewer. Use \`ask_user_input_v0\` for every question. **Do not ask all 4 at once.** Ask one (or at most two related ones) at a time, let their answer guide what you ask next.
111475
+
111476
+ ### Question arc
111477
+
111478
+ **Q1 \u2014 Role/Activity** (free text, always first)
111479
+
111480
+ > "What's your job, or what do you mainly spend your time doing?"
111481
+
111482
+ **Q2 \u2014 Hierarchy position** (single select) Options: Individual contributor / Team lead or manager / Senior leader or executive / Freelancer or solo / Other
111483
+
111484
+ **Q3 \u2014 Company domain and size** (two quick selects, ask together)
111485
+
111486
+ - Domain: Technology / Finance & legal / Healthcare / Education / Retail & commerce / Industrial & manufacturing / Public sector / Services & consulting / Other
111487
+ - Size: Just me / Small team (2\u201320) / Mid-size company (20\u2013500) / Large organization (500+)
111488
+
111489
+ **Q4 \u2014 Bonus question** (free text, optional feel)
111490
+
111491
+ > "Is there any part of your job you'd love to hand off to someone else \u2014 even partially?"
111492
+
111493
+ This last question is the golden one: it surfaces self-identified pain in their own words, which feeds directly into Phase 2.
111494
+
111495
+ ### Never skip Q4
111496
+
111497
+ Even if you feel you have enough context after Q3, always ask Q4. The bonus question often reveals the most actionable pain point and gives you their exact vocabulary to use in the skill.
111498
+
111499
+ ---
111500
+
111501
+ ## Phase 2: Suggest 3 Pain Points
111502
+
111503
+ Based on what you learned, **reason carefully** about what genuinely frustrates people in this role. Then propose exactly **3 pain points**, each one:
111504
+
111505
+ - A real, recurring frustration (not a vague category)
111506
+ - Solvable by Claude working with text, documents, or structured thinking
111507
+ - Requiring **no system integrations** (or at most: "paste text in" / "upload a file")
111508
+ - Described in plain human language
111509
+
111510
+ ### Format for each pain point card
111511
+
111512
+ Present them conversationally, like this:
111513
+
111514
+ ---
111515
+
111516
+ **1. [Short, vivid title]** *What happens:* [1 sentence describing the frustration in their words/world] *What a skill could do:* [1 concrete sentence \u2014 what Claude would actually produce] *Effort to set up:* Zero \u2014 just paste or upload
111517
+
111518
+ ---
111519
+
111520
+ After presenting all 3, ask:
111521
+
111522
+ > "Do any of these ring a bell? Pick the one that would save you the most time or headaches."
111523
+
111524
+ Use \`ask_user_input_v0\` with the 3 titles as options (plus "None of these feel right").
111525
+
111526
+ If they say "none of these", ask one follow-up: "What's the thing at work that you keep putting off because it feels tedious?" Then generate 3 new suggestions.
111527
+
111528
+ ---
111529
+
111530
+ ## Phase 3: Interview Before Writing
111531
+
111532
+ Once they've picked a pain point, **don't jump straight to writing the skill**. Ask 2\u20133 short, focused questions to understand:
111533
+
111534
+ 1. **A concrete example** \u2014 "Can you describe a real situation where this came up recently? Even roughly."
111535
+ 2. **What good looks like** \u2014 "When it goes well, what does the result look like? (An email? A summary? A checklist?)"
111536
+ 3. **The raw material** \u2014 "What do you usually start with? (A document, notes, an email thread, something else?)"
111537
+
111538
+ Keep these conversational. Use \`ask_user_input_v0\` where options make sense (e.g. output format), free text where you need specifics.
111539
+
111540
+ ---
111541
+
111542
+ ## Phase 4: Write the Skill Together
111543
+
111544
+ Now write a SKILL.md tailored to their pain point. Follow these principles:
111545
+
111546
+ ### Skill writing rules for non-technical users
111547
+
111548
+ - **Name**: Short, verb-first, plain English (e.g. \`contract-plain-language\`, \`meeting-notes-to-actions\`)
111549
+ - **Description**: Written so skilder knows exactly when to offer this skill \u2014 include the phrases this person would naturally say
111550
+ - **Instructions**: Written as if explaining to a smart colleague, not a machine. No jargon.
111551
+ - **Input**: Always "paste your text below" or "upload your document" \u2014 never assume integrations
111552
+ - **Output**: One clear, usable thing (a draft, a list, a summary, a set of questions \u2014 not "analysis")
111553
+
111554
+ ### Skill structure to follow
111555
+
111556
+ \`\`\`plaintext
111557
+ ---
111558
+ name: [verb-noun]
111559
+ description: >
111560
+ Use this skill when [person's role] needs to [specific task].
111561
+ Trigger when they say things like "[natural phrase 1]", "[natural phrase 2]".
111562
+ ---
111563
+
111564
+ # [Skill Name]
111565
+
111566
+ ## What this does
111567
+ [1\u20132 sentences. Plain language.]
111568
+
111569
+ ## How to use it
111570
+ [2\u20133 steps. Ultra simple. "Paste your text." "Tell skilder what you need." "Get your result."]
111571
+
111572
+ ## What skilder will produce
111573
+ [Describe the output concretely.]
111574
+
111575
+ ## Example
111576
+ Input: [short realistic example]
111577
+ Output: [what skilder would return]
111578
+ \`\`\`
111579
+
111580
+ ### After writing
111581
+
111582
+ Show them the full skill file and ask:
111583
+
111584
+ > "Does this feel right? Is there anything about your situation I've got wrong or missed?"
111585
+
111586
+ Incorporate their feedback, then present the final \`.skill\` file for adding to workspace. If they ask about integrations (ex. spreadsheet folder, email, other systems or database), verify the tool list to see what's configured and accessible. If nothing exist or match, you must suggest to save the skill as-is and add Tools later. NEVER encourage manual work.
111587
+
111588
+ ---
111589
+
111590
+ ## Tone reminders (apply throughout)
111591
+
111592
+ - Never say "workflow", "automate", "prompt", "LLM", "model", or "integrate"
111593
+ - Say "save you time", "take care of the tedious part", "handle the first draft"
111594
+ - If they seem unsure or intimidated, reassure: "You don't need to know anything technical \u2014 we're just figuring out what would be most useful for you."
111595
+ - Keep every message short. One idea at a time.`,
111596
+ related: []
111597
+ }
111598
+ ];
111599
+
111600
+ // ../common/src/skill-catalog-business.ts
111601
+ var BUSINESS_FEATURED_SKILLS = [
111602
+ {
111603
+ slug: "product-and-service-catalog",
111604
+ name: "Product & Service Catalog",
111605
+ description: `Use this skill to answer employee questions about what the company sells: products, services, descriptions, pricing, packages, and who owns each line. Trigger on questions like "what's the price of X", "do we offer Y", "describe service Z", "what's included in our Premium plan", "who do I talk to for X".`,
111606
+ tags: ["business", "reference", "template"],
111607
+ instructions: `Answer employee questions about the company's products and services using the catalog reference document.
111608
+
111609
+ ## How to use
111610
+
111611
+ 1. Read @references/product-catalog.md to find the matching product or service entry.
111612
+ 2. Quote the relevant fields directly (name, description, price, status, contact).
111613
+ 3. If the question is comparative ("which plan fits a small team?"), summarise the relevant rows in plain language \u2014 don't dump the full table.
111614
+ 4. If the data hasn't been filled in yet (placeholders like {Product Name} still present), tell the user the catalog hasn't been populated and point them at the template.
111615
+
111616
+ ## Constraints
111617
+
111618
+ - **Do not invent products, prices, or features.** If it isn't in the catalog, say so and offer to flag it for the catalog owner.
111619
+ - **Mention status fields** when present (e.g. "Discontinued", "Beta", "Coming soon"). Don't quote prices for inactive items without flagging the status.
111620
+ - **Internal pricing or margin info** stays internal \u2014 if the reference marks a row as "Confidential", do not share with external-facing roles without confirming the requester's permission.
111621
+
111622
+ ## Maintenance
111623
+
111624
+ The catalog template lives in @references/product-catalog.md. Replace placeholder rows with your real product lines; the structure is intentionally flat to make updates fast. Owners are expected to keep status and pricing accurate.`,
111625
+ related: [
111626
+ {
111627
+ name: "product-catalog.md",
111628
+ description: "Template product & service catalog \u2014 replace placeholder rows with real data.",
111629
+ mimetype: "text/markdown",
111630
+ content: `# Product & Service Catalog
111631
+
111632
+ > **Template \u2014 replace the example rows with your real products and services.** Keep the column structure so the agent can read the data reliably.
111633
+
111634
+ ## Active products & services
111635
+
111636
+ | Name | Category | Description | Price | Unit | Status | Owner |
111637
+ |---|---|---|---|---|---|---|
111638
+ | {Product A} | {Hardware / Software / Service} | {One-line description of what it does} | {0.00} | {per unit / per month / per project} | Active | {Owner name or team} |
111639
+ | {Product B} | {Category} | {Description} | {0.00} | {Unit} | Active | {Owner} |
111640
+ | {Service C} | Service | {Description} | {0.00} | per hour | Active | {Owner} |
111641
+
111642
+ ## Packages & bundles
111643
+
111644
+ | Bundle | Includes | Price | Notes |
111645
+ |---|---|---|---|
111646
+ | {Starter Pack} | {Product A + Product B} | {0.00} | {Volume discount, contract length, etc.} |
111647
+
111648
+ ## Inactive / archived
111649
+
111650
+ | Name | Status | Replaced by |
111651
+ |---|---|---|
111652
+ | {Old Product} | Discontinued ({date}) | {New Product} |
111653
+
111654
+ ## Maintenance notes
111655
+
111656
+ - Update the **Status** column whenever a product is launched, paused, or retired.
111657
+ - Mark rows **Confidential** in the Notes column if internal-only pricing should not be shared externally.
111658
+ - Keep one row per SKU \u2014 split bundles into the Packages section.
111659
+ `
111660
+ }
111661
+ ]
111662
+ },
111663
+ {
111664
+ slug: "customer-directory",
111665
+ name: "Customer Directory",
111666
+ description: `Use this skill to look up information about the company's customers: contact details, account manager, account status, recent interactions. Trigger on questions like "who is X", "what's the account manager for Y", "how do we contact Z", "what's the status of customer X", "when did we last talk to Y".`,
111667
+ tags: ["business", "reference", "template"],
111668
+ instructions: `Help employees find customer information using the customer directory reference document.
111669
+
111670
+ ## How to use
111671
+
111672
+ 1. Read @references/customer-directory.md to locate the customer by name, company, or account ID.
111673
+ 2. Share the requested field(s): contact, account manager, status, last interaction date.
111674
+ 3. For broader questions ("which customers are inactive?"), summarise the matching rows.
111675
+ 4. If the data still contains placeholders, tell the user the directory hasn't been populated yet.
111676
+
111677
+ ## Privacy & access
111678
+
111679
+ - @references/customer-access-policy.md defines what to share with whom. Follow it.
111680
+ - Default to sharing only **business contact info** (name, role, work email/phone, account status).
111681
+ - **Do not share** financial details, contract terms, or personal info to roles not listed as "Full access" in the access policy.
111682
+ - If unsure who has access, ask the requester to confirm their role before answering.
111683
+
111684
+ ## Constraints
111685
+
111686
+ - Don't invent or guess contact details. If a customer isn't listed, say so.
111687
+ - For sensitive lookups (account churn, contract value), confirm the requester's role first.
111688
+
111689
+ ## Maintenance
111690
+
111691
+ Keep @references/customer-directory.md up to date when account managers change or status flips. The access policy is the source of truth for who can see what.`,
111692
+ related: [
111693
+ {
111694
+ name: "customer-directory.md",
111695
+ description: "Template customer directory \u2014 replace placeholder rows with real customers.",
111696
+ mimetype: "text/markdown",
111697
+ content: `# Customer Directory
111698
+
111699
+ > **Template \u2014 replace the example rows with your real customer list.** Keep the columns so the agent can answer lookups reliably.
111700
+
111701
+ | Account ID | Company | Primary Contact | Role | Email | Phone | Account Manager | Status | Last Interaction | Notes |
111702
+ |---|---|---|---|---|---|---|---|---|---|
111703
+ | {ACME-001} | {Acme Corp} | {Jane Doe} | {Head of Ops} | {jane@acme.example} | {+1 555 0100} | {Account Manager Name} | Active | {YYYY-MM-DD} | {Renewal Q3} |
111704
+ | {GLOBEX-002} | {Globex} | {John Smith} | {VP Sales} | {john@globex.example} | {+1 555 0101} | {Account Manager Name} | At-risk | {YYYY-MM-DD} | {Pricing escalation open} |
111705
+ | {INITECH-003} | {Initech} | {Alex Wong} | {Procurement Lead} | {alex@initech.example} | {+1 555 0102} | {Account Manager Name} | Inactive | {YYYY-MM-DD} | {Contract ended {date}} |
111706
+
111707
+ ## Status legend
111708
+
111709
+ - **Active** \u2014 current paying customer
111710
+ - **At-risk** \u2014 flagged by account manager; escalation in flight
111711
+ - **Inactive** \u2014 no current contract; do not contact without approval
111712
+ - **Prospect** \u2014 in sales pipeline, not yet a customer
111713
+
111714
+ ## Maintenance notes
111715
+
111716
+ - Update **Status** and **Last Interaction** after each meaningful touchpoint.
111717
+ - Mark Notes as **Confidential** if they reference negotiation or churn risk.
111718
+ `
111719
+ },
111720
+ {
111721
+ name: "customer-access-policy.md",
111722
+ description: "Template access policy \u2014 who can see which customer fields.",
111723
+ mimetype: "text/markdown",
111724
+ content: `# Customer Information Access Policy
111725
+
111726
+ > **Template \u2014 replace placeholder roles with the actual roles in your workspace.** This policy controls what the Business Assistant will share with each requester.
111727
+
111728
+ ## Access levels
111729
+
111730
+ | Role | Contact info | Account status | Account manager | Financial / contract | Sensitive notes |
111731
+ |---|---|---|---|---|---|
111732
+ | {Sales team} | \u2705 | \u2705 | \u2705 | \u2705 | \u2705 |
111733
+ | {Customer support} | \u2705 | \u2705 | \u2705 | \u274C | \u274C |
111734
+ | {Operations / delivery} | \u2705 | \u2705 | \u2705 | \u274C | \u274C |
111735
+ | {General employee} | \u2705 business email only | \u2705 | \u2705 | \u274C | \u274C |
111736
+ | {External / unknown} | \u274C | \u274C | \u274C | \u274C | \u274C |
111737
+
111738
+ ## When to refuse
111739
+
111740
+ - The requester's role isn't listed \u2192 ask them to confirm their role first.
111741
+ - The customer is marked **Inactive** \u2192 require explicit approval from the listed account manager before sharing.
111742
+ - Notes are flagged **Confidential** \u2192 do not share unless the role has "Sensitive notes" access.
111743
+
111744
+ ## When to escalate
111745
+
111746
+ - Requests for bulk export of the directory \u2192 escalate to the data owner.
111747
+ - Repeated lookups across many customers from a single user \u2192 flag to the workspace admin.
111748
+ `
111749
+ }
111750
+ ]
111751
+ },
111752
+ {
111753
+ slug: "supplier-directory",
111754
+ name: "Supplier Directory",
111755
+ description: `Use this skill to look up information about the company's suppliers and vendors: who supplies what, contact details, contract end dates, lead times, payment terms. Trigger on questions like "who supplies X", "how do we contact supplier Y", "when does our contract with Z expire", "what's our lead time for component A".`,
111756
+ tags: ["business", "reference", "template"],
111757
+ instructions: `Answer employee questions about suppliers and vendors using the supplier directory reference document.
111758
+
111759
+ ## How to use
111760
+
111761
+ 1. Read @references/supplier-directory.md to find the supplier by name, category, or what they provide.
111762
+ 2. Share the requested fields: contact, contract end date, lead time, payment terms.
111763
+ 3. For "who supplies X" questions, scan the Category and What we buy columns; list all matches.
111764
+ 4. If the data still contains placeholders, tell the user the directory hasn't been populated yet.
111765
+
111766
+ ## Constraints
111767
+
111768
+ - **Don't invent suppliers or contract terms.** If something isn't listed, say so.
111769
+ - **Flag expiring contracts** proactively when the user asks about a supplier whose contract end date is within 60 days.
111770
+ - **Confidential pricing** (marked in the Notes column) should not be shared outside procurement without confirmation.
111771
+
111772
+ ## Maintenance
111773
+
111774
+ The directory at @references/supplier-directory.md is the source of truth. Procurement owners are expected to keep contract dates and contacts current; flag stale entries (no interaction in 12+ months) for review.`,
111775
+ related: [
111776
+ {
111777
+ name: "supplier-directory.md",
111778
+ description: "Template supplier directory \u2014 replace placeholder rows with real suppliers.",
111779
+ mimetype: "text/markdown",
111780
+ content: `# Supplier Directory
111781
+
111782
+ > **Template \u2014 replace the example rows with your real suppliers.** Keep the columns so the agent can answer lookups reliably.
111783
+
111784
+ | Supplier | Category | What we buy | Primary Contact | Email | Phone | Contract End | Lead Time | Payment Terms | Notes |
111785
+ |---|---|---|---|---|---|---|---|---|---|
111786
+ | {Supplier A} | {Hardware} | {Specific components} | {Contact name} | {contact@supplierA.example} | {+1 555 0200} | {YYYY-MM-DD} | {2 weeks} | {Net 30} | {Preferred vendor} |
111787
+ | {Supplier B} | {Services} | {Cleaning, facilities} | {Contact name} | {contact@supplierB.example} | {+1 555 0201} | {YYYY-MM-DD} | {N/A} | {Net 15} | {Auto-renews annually} |
111788
+ | {Supplier C} | {Software / SaaS} | {Specific tool} | {Contact name} | {contact@supplierC.example} | {+1 555 0202} | {YYYY-MM-DD} | {Immediate} | {Annual prepay} | {Confidential pricing} |
111789
+
111790
+ ## Category legend
111791
+
111792
+ Common categories: Hardware, Software / SaaS, Services, Logistics, Marketing, Professional services, Utilities.
111793
+
111794
+ ## Maintenance notes
111795
+
111796
+ - Update **Contract End** whenever a renewal or termination happens.
111797
+ - Mark Notes as **Confidential** for negotiated pricing or sensitive terms.
111798
+ - Archive (don't delete) ended suppliers \u2014 keep a row with Status: Archived for institutional memory.
111799
+ `
111800
+ }
111801
+ ]
111802
+ },
111803
+ {
111804
+ slug: "team-weekly-planning",
111805
+ name: "Team Weekly Planning",
111806
+ description: `Use this skill to answer questions about who's working on what this week, who's on leave, project assignments, and team availability. Trigger on questions like "who's working on X this week", "is Y available", "who's on PTO this week", "what's the team doing", "who can I ask about project Z".`,
111807
+ tags: ["business", "reference", "template"],
111808
+ instructions: `Answer employee questions about team availability and assignments using the weekly planning reference document.
111809
+
111810
+ ## How to use
111811
+
111812
+ 1. Read @references/weekly-planning.md for the current week's assignments and availability.
111813
+ 2. For "who's working on X", list the assigned team members and their availability for the week.
111814
+ 3. For "is Y available", report the requested person's PTO and assignment load for the week.
111815
+ 4. For "who's on PTO", list everyone with a PTO entry for the requested week.
111816
+ 5. If the planning is stale (week marker is in the past), call this out and recommend asking the team lead for the current week.
111817
+
111818
+ ## Constraints
111819
+
111820
+ - **Don't invent assignments or PTO.** If someone isn't listed, say so and suggest asking their manager.
111821
+ - **The planning reflects intent, not real-time status.** If the user needs live availability (e.g. "can Y join a call now?"), tell them to message the person directly.
111822
+ - **PTO reasons stay private.** Share "on PTO" but not the reason unless explicitly listed as public.
111823
+
111824
+ ## Maintenance
111825
+
111826
+ The planning at @references/weekly-planning.md should be refreshed weekly (typically at week start by the team lead). Stale planning is worse than no planning \u2014 flag week dates older than 7 days for refresh.`,
111827
+ related: [
111828
+ {
111829
+ name: "weekly-planning.md",
111830
+ description: "Template weekly planning \u2014 replace placeholder rows with real assignments.",
111831
+ mimetype: "text/markdown",
111832
+ content: `# Team Weekly Planning
111833
+
111834
+ > **Template \u2014 replace placeholders with this week's real assignments.** Update at the start of each week (typically Monday).
111835
+
111836
+ ## Week of {YYYY-MM-DD}
111837
+
111838
+ ### Assignments
111839
+
111840
+ | Person | Role | Primary project | Other work | Availability | Notes |
111841
+ |---|---|---|---|---|---|
111842
+ | {Alice} | {Engineer} | {Project Alpha} | {Code reviews, on-call Tue} | Full week | {Focus blocks 9\u201312 daily} |
111843
+ | {Bob} | {Designer} | {Project Beta} | {Design system} | Mon\u2013Wed | {PTO Thu\u2013Fri} |
111844
+ | {Carol} | {PM} | {Project Alpha, Project Beta} | {Roadmap planning} | Full week | {} |
111845
+ | {Dan} | {Engineer} | \u2014 | \u2014 | PTO full week | {Back {YYYY-MM-DD}} |
111846
+
111847
+ ### Out of office this week
111848
+
111849
+ | Person | Dates | Cover |
111850
+ |---|---|---|
111851
+ | {Dan} | {Mon\u2013Fri} | {Alice covers on-call} |
111852
+ | {Bob} | {Thu\u2013Fri} | {N/A \u2014 async} |
111853
+
111854
+ ### Project leads (current quarter)
111855
+
111856
+ | Project | Lead | Backup |
111857
+ |---|---|---|
111858
+ | {Project Alpha} | {Carol} | {Alice} |
111859
+ | {Project Beta} | {Carol} | {Bob} |
111860
+
111861
+ ## Maintenance notes
111862
+
111863
+ - Update **Week of** date every Monday. Stale dates make the agent give wrong answers.
111864
+ - Use **PTO**, **Half-day**, **Remote-only** in the Availability column for quick scanning.
111865
+ - Keep Project leads stable across the quarter \u2014 they're how people know who to escalate to.
111866
+ `
111867
+ }
111868
+ ]
111869
+ }
111870
+ ];
111871
+
111872
+ // ../common/src/skill-catalog.ts
111873
+ var FEATURED_SKILLS = [
111874
+ ...SKILDER_META_FEATURED_SKILLS,
111875
+ ...ONBOARDING_FEATURED_SKILLS,
111876
+ ...BUSINESS_FEATURED_SKILLS
111877
+ ];
111878
+
111879
+ // ../common/src/hat-catalog.ts
111880
+ var FEATURED_HATS = [
111881
+ {
111882
+ slug: "business-assistant",
111883
+ name: "Business Assistant",
111884
+ description: "Central internal info desk: any employee can ask about products, customers, suppliers, or this week's team plan and get a fast, sourced answer.",
111885
+ color: "BLUE",
111886
+ tags: ["business", "reference", "template"],
111887
+ skillSlugs: [
111888
+ "product-and-service-catalog",
111889
+ "customer-directory",
111890
+ "supplier-directory",
111891
+ "team-weekly-planning"
111892
+ ],
111893
+ instructions: `You are the company's internal Business Assistant. Any employee can ask you about what we sell, who our customers and suppliers are, and what the team is working on this week. Your job is to give a fast, sourced answer \u2014 or to clearly say when you don't have the data.
111894
+
111895
+ ## Which skill to use
111896
+
111897
+ - **What we sell** (products, services, pricing, packages) \u2192 Product & Service Catalog
111898
+ - **A specific customer** (contact, account manager, status) \u2192 Customer Directory
111899
+ - **A supplier or vendor** (contact, contract, lead time) \u2192 Supplier Directory
111900
+ - **Who's working on what / who's available** \u2192 Team Weekly Planning
111901
+
111902
+ If a question spans skills (e.g. "which account manager handles Acme, and is she on PTO?"), use both skills in turn and combine the answer.
111903
+
111904
+ ## Operating principles
111905
+
111906
+ - **Source every answer.** Quote the relevant rows from the reference document, don't paraphrase from memory.
111907
+ - **Don't invent data.** If the requested item isn't in the reference, say so plainly and suggest who owns the catalog so the user can ask for it to be added.
111908
+ - **Respect access policies.** The Customer Directory skill carries an access policy \u2014 follow it. If unsure who can see what, ask before sharing.
111909
+ - **Flag stale templates.** If a reference still contains placeholder values (\`{Product Name}\`, \`{Acme Corp}\`), tell the user the workspace hasn't populated it yet and point them at the template.
111910
+ - **Be concise.** Employees want a quick answer, not a wall of text. One or two lines for simple lookups; a short list for "who" or "which" questions.
111911
+
111912
+ ## Template hat \u2014 first-time setup
111913
+
111914
+ This hat ships as a template. The bundled skills each include a reference document with placeholder content. Before this assistant is useful in your workspace, an owner needs to fill in the actual product list, customer directory, supplier directory, and weekly planning. The templates are intentionally simple markdown tables \u2014 open each skill's references tab and replace placeholders with your real data.`
111915
+ },
111916
+ {
111917
+ slug: SKILDER_HAT_DEFAULTS.slug,
111918
+ name: SKILDER_HAT_DEFAULTS.name,
111919
+ description: SKILDER_HAT_DEFAULTS.description,
111920
+ color: SKILDER_HAT_DEFAULTS.color,
111921
+ tags: ["skilder", "meta", "admin"],
111922
+ skillSlugs: [...SKILDER_SKILL_IDS],
111923
+ instructions: SKILDER_HAT_DEFAULTS.instructions
111924
+ }
111925
+ ];
111926
+
110768
111927
  // ../common/src/services/logger.service.ts
110769
111928
  var import_pino = __toESM(require_pino(), 1);
110770
111929
  var import_pino_pretty = __toESM(require_pino_pretty(), 1);
@@ -111426,6 +112585,51 @@ var EncryptionService = class EncryptionService2 {
111426
112585
  isEncrypted(value) {
111427
112586
  return value.startsWith(ENCRYPTED_PREFIX);
111428
112587
  }
112588
+ /**
112589
+ * Compute a SHA-256 hash (hex) of an opaque value.
112590
+ *
112591
+ * Used for one-way hashing of high-entropy secrets (OAuth access/refresh tokens,
112592
+ * authorization codes, client secrets, registration access tokens) so that only
112593
+ * the hash is stored at rest and lookups happen by hash. Plain SHA-256 is
112594
+ * appropriate here because the inputs are 256-bit random opaque values with no
112595
+ * brute-force surface; this is NOT for hashing low-entropy user passwords.
112596
+ */
112597
+ sha256Hash(value) {
112598
+ return (0, import_crypto.createHash)("sha256").update(value).digest("hex");
112599
+ }
112600
+ /**
112601
+ * Constant-time equality of two strings compared as UTF-8 bytes. Returns false
112602
+ * on any length mismatch (and for empty input). Use for comparing fixed-length,
112603
+ * high-entropy values that are already in their final comparable form — e.g. a
112604
+ * recomputed PKCE S256 challenge against the stored one — without leaking
112605
+ * content via timing. For comparing a raw secret against a stored hash, use
112606
+ * {@link verifySha256Hash} instead.
112607
+ */
112608
+ constantTimeEquals(a3, b2) {
112609
+ const ab = Buffer.from(a3, "utf8");
112610
+ const bb = Buffer.from(b2, "utf8");
112611
+ if (ab.length !== bb.length || ab.length === 0) {
112612
+ return false;
112613
+ }
112614
+ return (0, import_crypto.timingSafeEqual)(ab, bb);
112615
+ }
112616
+ /**
112617
+ * Constant-time comparison of a raw value against a stored SHA-256 hex hash.
112618
+ * Avoids timing side channels when verifying secrets.
112619
+ */
112620
+ verifySha256Hash(rawValue, storedHash) {
112621
+ const computed = Buffer.from(this.sha256Hash(rawValue), "hex");
112622
+ let expected;
112623
+ try {
112624
+ expected = Buffer.from(storedHash, "hex");
112625
+ } catch {
112626
+ return false;
112627
+ }
112628
+ if (computed.length !== expected.length || expected.length === 0) {
112629
+ return false;
112630
+ }
112631
+ return (0, import_crypto.timingSafeEqual)(computed, expected);
112632
+ }
111429
112633
  };
111430
112634
  EncryptionService = __decorate3([
111431
112635
  W(),
@@ -139057,7 +140261,8 @@ function detectRelatedMentions(content, related) {
139057
140261
  }
139058
140262
  searchTerms.push(r2.name);
139059
140263
  for (const term of searchTerms) {
139060
- if (seen.has(qualifiedName)) break;
140264
+ if (seen.has(qualifiedName))
140265
+ break;
139061
140266
  try {
139062
140267
  const pattern = buildSearchPattern(term);
139063
140268
  if (pattern.test(content)) {
@@ -139069,17 +140274,12 @@ function detectRelatedMentions(content, related) {
139069
140274
  });
139070
140275
  }
139071
140276
  } catch (error48) {
139072
- console.warn(
139073
- `[detectRelatedMentions] Failed to match term "${term}" for resource "${r2.name}":`,
139074
- error48
139075
- );
140277
+ console.warn(`[detectRelatedMentions] Failed to match term "${term}" for resource "${r2.name}":`, error48);
139076
140278
  continue;
139077
140279
  }
139078
140280
  }
139079
140281
  }
139080
- return Array.from(seen.values()).sort(
139081
- (a3, b2) => a3.qualifiedName.localeCompare(b2.qualifiedName)
139082
- );
140282
+ return Array.from(seen.values()).sort((a3, b2) => a3.qualifiedName.localeCompare(b2.qualifiedName));
139083
140283
  }
139084
140284
 
139085
140285
  // src/utils.ts
@@ -144219,6 +145419,25 @@ var path2 = __toESM(require("path"));
144219
145419
  var import_node_crypto3 = require("node:crypto");
144220
145420
  var import_promises = require("node:stream/promises");
144221
145421
  var import_node_stream = require("node:stream");
145422
+
145423
+ // src/config/urls.ts
145424
+ function publicMcpUrl() {
145425
+ return parseUrlEnv("PUBLIC_MCP_URL", "http://localhost:3001/mcp");
145426
+ }
145427
+ function publicAuthorizationServerUrl() {
145428
+ return parseUrlEnv("PUBLIC_BACKEND_URL", "http://localhost:3000");
145429
+ }
145430
+ function privateMcpUrl() {
145431
+ return parseUrlEnv("PRIVATE_MCP_URL", "http://localhost:3001/mcp");
145432
+ }
145433
+ function privateBackendUrl() {
145434
+ return parseUrlEnv("PRIVATE_BACKEND_URL", "http://localhost:3000");
145435
+ }
145436
+ function natsServers() {
145437
+ return parseUrlEnv("NATS_SERVERS", "localhost:4222");
145438
+ }
145439
+
145440
+ // src/services/file.service.ts
144222
145441
  var __decorate8 = function(decorators, target, key, desc) {
144223
145442
  var c3 = arguments.length, r2 = c3 < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d2;
144224
145443
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r2 = Reflect.decorate(decorators, target, key, desc);
@@ -144294,8 +145513,9 @@ var FileService = class FileService2 extends LifecycleService {
144294
145513
  SKILDER_ALLOWED_PATHS: this.allowedPaths.join(":"),
144295
145514
  SKILDER_MAX_FILE_SIZE_MB: String(this.maxFileSizeMB)
144296
145515
  };
144297
- if (process.env.SKILDER_BACKEND_URL) {
144298
- env.SKILDER_BACKEND_URL = process.env.SKILDER_BACKEND_URL;
145516
+ const backendUrl = process.env.SKILDER_BACKEND_URL || (process.env.PRIVATE_BACKEND_URL ? privateBackendUrl() : void 0);
145517
+ if (backendUrl) {
145518
+ env.SKILDER_BACKEND_URL = backendUrl;
144299
145519
  }
144300
145520
  return env;
144301
145521
  }
@@ -144595,9 +145815,9 @@ var DelegateService = class DelegateService2 {
144595
145815
  const modelString = modelOverride || defaultModel;
144596
145816
  const { slug, modelName } = this.aiProviderService.parseModelString(modelString);
144597
145817
  const model = this.aiProviderService.getProviderModel(provider, modelName, providerConfig, slug);
144598
- const runtimeUrl = process.env.RUNTIME_MCP_URL || "http://localhost:3001/mcp";
144599
- if (!process.env.RUNTIME_MCP_URL) {
144600
- this.logger.warn({ runtimeUrl, event: "delegate_mcp_url_fallback" }, "RUNTIME_MCP_URL not set, falling back to default");
145818
+ const runtimeUrl = privateMcpUrl();
145819
+ if (!process.env.PRIVATE_MCP_URL) {
145820
+ this.logger.warn({ runtimeUrl, event: "delegate_mcp_url_fallback" }, "PRIVATE_MCP_URL not set, falling back to default");
144601
145821
  }
144602
145822
  if (!userKey) {
144603
145823
  return { isError: true, content: [{ type: "text", text: "Error: User key not available for delegate MCP connection." }] };
@@ -145187,7 +146407,7 @@ var _a35;
145187
146407
  var CONTENT_CACHE_LRU_SIZE = 256;
145188
146408
  var CONTENT_CACHE_TTL_MS = 3e5;
145189
146409
  function getBackendUrl2() {
145190
- return process.env.SKILDER_BACKEND_URL || "http://localhost:3000";
146410
+ return privateBackendUrl();
145191
146411
  }
145192
146412
  var SkillContentService = class SkillContentService2 {
145193
146413
  constructor(loggerService) {
@@ -145847,7 +147067,7 @@ ${JSON.stringify(toolConfig.inputSchema, null, 2)}`
145847
147067
  // src/services/static-tools/download-asset.handler.ts
145848
147068
  var import_node_stream2 = require("node:stream");
145849
147069
  function getBackendUrl3() {
145850
- return (process.env.SKILDER_BACKEND_URL || "http://localhost:3000").replace(/\/$/, "");
147070
+ return privateBackendUrl();
145851
147071
  }
145852
147072
  var DownloadAssetError = class extends Error {
145853
147073
  constructor(userMessage, skillId) {
@@ -146090,7 +147310,7 @@ var MIME_BY_EXTENSION = {
146090
147310
  ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
146091
147311
  };
146092
147312
  function getBackendUrl4() {
146093
- return (process.env.SKILDER_BACKEND_URL || "http://localhost:3000").replace(/\/$/, "");
147313
+ return privateBackendUrl();
146094
147314
  }
146095
147315
  function mimeFromExtension(filePath) {
146096
147316
  const ext = path4.extname(filePath).toLowerCase();
@@ -147289,7 +148509,11 @@ var FastifyManagerService = class FastifyManagerService2 extends LifecycleServic
147289
148509
  // Allow credentials (cookies, authorization headers, etc.)
147290
148510
  methods: ["GET", "POST", "OPTIONS", "PUT", "DELETE"],
147291
148511
  // Explicitly allow methods including GET for SSE
147292
- exposedHeaders: ["Mcp-Session-Id"],
148512
+ // WWW-Authenticate must be exposed so browser-based MCP clients (e.g. the
148513
+ // MCP Inspector in direct mode) can read the RFC 9728 `resource_metadata`
148514
+ // challenge on a 401 and discover the Authorization Server. Without it the
148515
+ // browser hides the header and the client falls back to the wrong origin.
148516
+ exposedHeaders: ["Mcp-Session-Id", "WWW-Authenticate"],
147293
148517
  allowedHeaders: [
147294
148518
  "Content-Type",
147295
148519
  "mcp-session-id",
@@ -148376,7 +149600,7 @@ function injectCorsHeaders(request, reply) {
148376
149600
  }
148377
149601
  finalHeaders["Access-Control-Allow-Origin"] = origin;
148378
149602
  finalHeaders["Access-Control-Allow-Credentials"] = "true";
148379
- finalHeaders["Access-Control-Expose-Headers"] = "Mcp-Session-Id";
149603
+ finalHeaders["Access-Control-Expose-Headers"] = "Mcp-Session-Id, WWW-Authenticate";
148380
149604
  if (finalStatusMessage) {
148381
149605
  return originalWriteHead(statusCode, finalStatusMessage, finalHeaders);
148382
149606
  } else {
@@ -148507,12 +149731,21 @@ function getStringHeader(headers, name21) {
148507
149731
  const value = headers[name21];
148508
149732
  return typeof value === "string" && value ? value : void 0;
148509
149733
  }
149734
+ function getBearerToken(headers) {
149735
+ const value = headers["authorization"];
149736
+ if (typeof value !== "string") {
149737
+ return void 0;
149738
+ }
149739
+ const match = /^Bearer\s+(.+)$/i.exec(value.trim());
149740
+ const token = match?.[1].trim();
149741
+ return token ? token : void 0;
149742
+ }
148510
149743
  function extractAuthHeaders(request) {
148511
149744
  const headers = request.headers;
148512
149745
  const query = request.query;
148513
149746
  const skillIds = query?.skillIds?.split(",").filter(Boolean);
148514
149747
  const hatIds = query?.hatIds?.split(",").filter(Boolean);
148515
- const userKey = query?.key || getStringHeader(headers, "user_key") || getStringHeader(headers, "x-api-key");
149748
+ const userKey = query?.key || getBearerToken(headers) || getStringHeader(headers, "user_key") || getStringHeader(headers, "x-api-key");
148516
149749
  const rawDepth = getStringHeader(headers, "x-delegate-depth");
148517
149750
  const parsedDepth = rawDepth != null ? parseInt(rawDepth, 10) : void 0;
148518
149751
  const delegateDepth = parsedDepth != null && Number.isFinite(parsedDepth) && parsedDepth >= 0 ? Math.min(parsedDepth, MAX_DELEGATE_DEPTH) : void 0;
@@ -148589,6 +149822,37 @@ function getSessionForRequest(sessionId, sessions) {
148589
149822
  return session;
148590
149823
  }
148591
149824
 
149825
+ // src/helpers/oauth-challenge.helper.ts
149826
+ function getPublicMcpUrl() {
149827
+ return publicMcpUrl();
149828
+ }
149829
+ function getPublicAuthorizationServerUrl() {
149830
+ return publicAuthorizationServerUrl();
149831
+ }
149832
+ function getProtectedResourceMetadataUrl() {
149833
+ const u2 = new URL(getPublicMcpUrl());
149834
+ const resourcePath = u2.pathname.replace(/\/+$/, "");
149835
+ return `${u2.origin}/.well-known/oauth-protected-resource${resourcePath}`;
149836
+ }
149837
+ function getProtectedResourceMetadata() {
149838
+ return {
149839
+ resource: getPublicMcpUrl(),
149840
+ authorization_servers: [getPublicAuthorizationServerUrl()],
149841
+ scopes_supported: ["mcp"],
149842
+ bearer_methods_supported: ["header"]
149843
+ };
149844
+ }
149845
+ function getOAuthChallengeHeaderValue() {
149846
+ return `Bearer resource_metadata="${getProtectedResourceMetadataUrl()}"`;
149847
+ }
149848
+ function setOAuthChallengeHeader(reply) {
149849
+ reply.header("WWW-Authenticate", getOAuthChallengeHeaderValue());
149850
+ }
149851
+ function sendOAuthDiscoveryChallenge(reply, message = "Authentication required") {
149852
+ setOAuthChallengeHeader(reply);
149853
+ sendJsonRpcError(reply, JsonRpcErrorCode.SERVER_ERROR, message, 401);
149854
+ }
149855
+
148592
149856
  // src/helpers/mcp-handlers.helper.ts
148593
149857
  var McpRequestHandlers = class {
148594
149858
  /**
@@ -148873,6 +150137,11 @@ var McpSseService = class McpSseService2 extends LifecycleService {
148873
150137
  if (!this.validateSecurityHeaders(request, reply)) {
148874
150138
  return;
148875
150139
  }
150140
+ if (!extractAuthHeaders(request).userKey) {
150141
+ this.logger.debug("Unauthenticated /sse request \u2014 returning OAuth discovery challenge");
150142
+ sendOAuthDiscoveryChallenge(reply);
150143
+ return;
150144
+ }
148876
150145
  const identity = await authenticateSession(request, this.loggerService, this.natsService, this.runtimeExecutionId);
148877
150146
  this.logger.info(`Authenticated SSE connection for skill: ${identity.name}`);
148878
150147
  const skillService = await createSkillService(identity, this.loggerService, this.natsService, this.cacheService, this.healthService, this.scriptService, this.delegateService, this.fileService, this.skillContentService, this.staticToolRegistry, this.runtimeCatalogService, this.authService?.getIdentity()?.id ?? void 0);
@@ -148893,6 +150162,7 @@ var McpSseService = class McpSseService2 extends LifecycleService {
148893
150162
  } catch (error48) {
148894
150163
  this.logger.error({ err: error48 }, "Error handling SSE connection");
148895
150164
  if (error48 instanceof Error && error48.message.includes("Authentication failed")) {
150165
+ setOAuthChallengeHeader(reply);
148896
150166
  sendJsonRpcError(reply, JsonRpcErrorCode.SERVER_ERROR, error48.message, 401);
148897
150167
  } else if (error48 instanceof Error && error48.message.includes("Invalid")) {
148898
150168
  sendJsonRpcError(reply, JsonRpcErrorCode.INVALID_REQUEST, error48.message, 400);
@@ -150291,8 +151561,23 @@ var McpStreamableService = class McpStreamableService2 extends LifecycleService
150291
151561
  async setupTransport() {
150292
151562
  this.logger.info(`Setting up ${this.name} transport`);
150293
151563
  this.registerMcpRoute();
151564
+ this.registerProtectedResourceMetadataRoute();
150294
151565
  this.logger.info(`${this.name} transport configured`);
150295
151566
  }
151567
+ /**
151568
+ * Register the RFC 9728 Protected Resource Metadata endpoint on this MCP
151569
+ * server's own origin. Public + unauthenticated by design. We serve both the
151570
+ * root well-known path and the path-aware variants (`/.well-known/oauth-protected-resource/*`)
151571
+ * so clients that derive the metadata URL from the resource path also resolve.
151572
+ */
151573
+ registerProtectedResourceMetadataRoute() {
151574
+ const fastify2 = this.fastifyManager.getInstance();
151575
+ const handler = async (_request, reply) => {
151576
+ return reply.header("cache-control", "no-store").send(getProtectedResourceMetadata());
151577
+ };
151578
+ fastify2.get("/.well-known/oauth-protected-resource", handler);
151579
+ fastify2.get("/.well-known/oauth-protected-resource/*", handler);
151580
+ }
150296
151581
  /**
150297
151582
  * Register the /mcp route handler for streamable HTTP transport.
150298
151583
  * Handles GET (listen streams), POST (JSON-RPC messages), and DELETE (session termination).
@@ -150468,6 +151753,14 @@ var McpStreamableService = class McpStreamableService2 extends LifecycleService
150468
151753
  * Creates a new session for an authenticated request
150469
151754
  */
150470
151755
  async createNewSession(request, reply) {
151756
+ if (!extractAuthHeaders(request).userKey) {
151757
+ this.logger.debug("Unauthenticated /mcp request \u2014 returning OAuth discovery challenge");
151758
+ setOAuthChallengeHeader(reply);
151759
+ return reply.status(401).send({
151760
+ error: "Unauthorized",
151761
+ message: "Authentication required"
151762
+ });
151763
+ }
150471
151764
  try {
150472
151765
  const identity = await authenticateSession(request, this.loggerService, this.natsService, this.runtimeExecutionId);
150473
151766
  this.logger.info(`Authenticated new stream connection for skill: ${identity.name}`);
@@ -150481,6 +151774,7 @@ var McpStreamableService = class McpStreamableService2 extends LifecycleService
150481
151774
  const isAuthError = error48 instanceof Error && (msg.includes("unauthorized") || msg.includes("authentication") || msg.includes("forbidden"));
150482
151775
  if (isAuthError) {
150483
151776
  this.logger.error({ err: error48 }, "Authentication failed");
151777
+ setOAuthChallengeHeader(reply);
150484
151778
  return reply.status(401).send({
150485
151779
  error: "Unauthorized",
150486
151780
  message: `Authentication failed: ${error48 instanceof Error ? error48.message : String(error48)}`
@@ -156565,10 +157859,9 @@ var start = () => {
156565
157859
  }
156566
157860
  const randomSuffix = (0, import_crypto3.randomBytes)(2).toString("hex");
156567
157861
  const runtimeId = "runtime:" + randomSuffix;
156568
- const natsServers = process.env.NATS_SERVERS || "localhost:4222";
156569
157862
  const natsName = process.env.NATS_NAME || runtimeId;
156570
157863
  container.bind(NATS_CONNECTION_OPTIONS).toConstantValue({
156571
- servers: natsServers,
157864
+ servers: natsServers(),
156572
157865
  name: natsName,
156573
157866
  reconnect: true,
156574
157867
  maxReconnectAttempts: -1,