@theokit/sdk 2.24.0 → 2.25.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.25.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5067b50: **SE20 — `agent.skills.get(name)` (read a skill's full body).**
8
+
9
+ `agent.skills.list()` already returned skill metadata (name + description only); SE20 adds `agent.skills.get(name)` returning the skill INCLUDING its `instructions` (body) — read from the inline `createSkill` body, or from the filesystem SKILL.md (frontmatter stripped) for discovered skills. Returns `undefined` when no enabled skill matches (malformed skills stay excluded). New public type `SDKAgentSkillDetail`.
10
+
11
+ `list()` stays lean (the `<skills>` block only ever carries name + description); full bodies come only through `get`. Mirrors Mastra's `agent.getSkill(name)`. Additive + backward-compatible. From the Mastra Agent-skills comparison (SDK Evolution roadmap SE20).
12
+
13
+ - 09865ee: **SE21 — `references` on `createSkill` (bundle supporting docs on an inline skill).**
14
+
15
+ `createSkill({ ..., references })` now accepts an optional `references` map (filename → content), mirroring a filesystem skill's `references/` directory. The docs travel on the inline skill object and surface to the app via `agent.skills.get(name)` (new `references` field on `SDKAgentSkillDetail`); they are NOT injected into the model prompt. Omitted when not provided (backward-compatible). Mirrors Mastra Agent-skills `references`.
16
+
17
+ Also closes a latent boundary leak surfaced by this change: `agent.skills.list()` now projects to the public shape (name + description only), so an inline skill's `instructions` / `references` / `source` never leak through `list()` — the body is reachable exclusively through `get()`, matching the documented `SystemPromptSkillRef` contract. From the Mastra Agent-skills comparison (SDK Evolution roadmap SE21).
18
+
19
+ - abfcc5d: **SE22 — dynamic skills resolver (`skills: (ctx) => SkillsSettings`).**
20
+
21
+ `AgentOptions.skills` now accepts a resolver function in addition to the static `SkillsSettings` object. The resolver receives a per-send context (`agentId`, `cwd`, `model`, `userMessage`, `memory` — mirroring the systemPrompt resolver's context, minus the not-yet-resolved `skills`) and returns the `SkillsSettings` for that run. It is evaluated per `send()` before skill assembly, so a cached `getOrCreate` agent re-resolves each run — pick skills from runtime context (e.g. the user's role).
22
+
23
+ A static object behaves exactly as today. The agent-scoped `agent.skills` handle reflects the static/base config; the resolver drives the per-send `<skills>` block. The SDK imposes no timeout (wrap your own `Promise.race`); a throwing resolver fails the run — no silent fallback (Rule 8). Cloud agents reject a function resolver (it can't run on PaaS — resolve to a static object first), mirroring the systemPrompt-resolver cloud rule. New public types `SkillsResolver` + `SkillsResolverContext`. Mirrors Mastra Agent-skills `skills: ({ requestContext }) => SkillInput[]`. From the Mastra Agent-skills comparison (SDK Evolution roadmap SE22).
24
+
25
+ - 0b9c0ac: **SE23 — `defineSkillReadTool` (opt-in model-facing lazy skill read).**
26
+
27
+ `defineSkillReadTool(skills)` returns a `skill_read` `CustomTool` the consumer explicitly adds to `AgentOptions.tools`. When the model calls it with a skill name, the handler returns that skill's `instructions` (+ SE21 `references`); an unknown-but-well-formed name returns a typed "not found" string listing the available skills — NOT a throw that kills the run (Rule 8). Malformed input (missing `name`) fails at the trust boundary via the input schema.
28
+
29
+ The SDK never auto-injects it — bring-your-own-tools stays intact (sibling of `defineSubAgent` / `workflowAsTool`). This is the LAZY read path that complements the eager `<skills>` block (name + description only): the block discloses which skills exist; `skill_read` loads a body on demand. The consumer controls exposure by choosing which skills to pass. See ADR 0007. Mirrors Mastra's `skill_read` — but opt-in, not auto-injected. From the Mastra Agent-skills comparison (SDK Evolution roadmap SE23).
30
+
3
31
  ## 2.24.0
4
32
 
5
33
  ### Minor Changes
@@ -1553,7 +1553,8 @@ function stripSecretsFromOptions(options) {
1553
1553
  local: serializeLocal(options.local),
1554
1554
  cloud: serializeCloud(options.cloud),
1555
1555
  memory: serializeMemory(options.memory),
1556
- skills: serializeEnabledList(options.skills),
1556
+ // SE22 — a SkillsResolver function isn't serializable; persist only the static form.
1557
+ skills: serializeEnabledList(typeof options.skills === "function" ? void 0 : options.skills),
1557
1558
  // Code-`Plugin` objects are closures and cannot be persisted (like custom
1558
1559
  // tools); only the named-enable settings form is serialized.
1559
1560
  plugins: serializeEnabledList(asPluginsSettings(options.plugins)),
@@ -1883,6 +1884,7 @@ function serializeCloud2(cloud) {
1883
1884
  return result;
1884
1885
  }
1885
1886
  function serializeSkills(skills) {
1887
+ if (typeof skills === "function") return void 0;
1886
1888
  if (skills?.enabled === void 0 || skills.enabled.length === 0) return void 0;
1887
1889
  return { enabled: [...skills.enabled] };
1888
1890
  }
@@ -3451,6 +3453,7 @@ var init_cloud_agent = __esm({
3451
3453
  function validateCloudToolParity(options) {
3452
3454
  if (options.cloud === void 0) return;
3453
3455
  rejectFunctionSystemPrompt(options);
3456
+ rejectFunctionSkills(options);
3454
3457
  rejectStdioMcpLocalPaths(options);
3455
3458
  }
3456
3459
  function rejectFunctionSystemPrompt(options) {
@@ -3461,6 +3464,14 @@ function rejectFunctionSystemPrompt(options) {
3461
3464
  );
3462
3465
  }
3463
3466
  }
3467
+ function rejectFunctionSkills(options) {
3468
+ if (typeof options.skills === "function") {
3469
+ throw new ConfigurationError(
3470
+ "Cloud agents require skills as a static settings object. SkillsResolver functions can't run on PaaS \u2014 resolve to a SkillsSettings object before Agent.create() or move the dynamic logic into a hook rule.",
3471
+ { code: "cloud_incompatible_function_resolver" }
3472
+ );
3473
+ }
3474
+ }
3464
3475
  function rejectStdioMcpLocalPaths(options) {
3465
3476
  if (options.mcpServers === void 0) return;
3466
3477
  for (const [name, config] of Object.entries(options.mcpServers)) {
@@ -5693,9 +5704,235 @@ var init_subagents_loader = __esm({
5693
5704
  }
5694
5705
  });
5695
5706
 
5707
+ // src/internal/runtime/skills/skill-frontmatter.ts
5708
+ function asString(v) {
5709
+ return typeof v === "string" ? v : void 0;
5710
+ }
5711
+ function toStringFields(raw) {
5712
+ const out = {};
5713
+ for (const [k, v] of Object.entries(raw)) out[k] = asString(v);
5714
+ return out;
5715
+ }
5716
+ function parseSkillFrontmatter(raw, fallbackName) {
5717
+ const fields = extractAndParseFrontmatter(raw, fallbackName);
5718
+ const name = resolveName(fields, fallbackName);
5719
+ ensureRequiredFields(fields, name);
5720
+ return buildFrontmatter(fields, name);
5721
+ }
5722
+ function stripSkillFrontmatter(raw) {
5723
+ const match = /^---\s*\n[\s\S]*?\n---\s*\n/.exec(raw);
5724
+ return (match === null ? raw : raw.slice(match[0].length)).trim();
5725
+ }
5726
+ function extractAndParseFrontmatter(raw, fallbackName) {
5727
+ const match = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
5728
+ if (match === null) {
5729
+ throw new ConfigurationError(`Skill ${fallbackName} is missing frontmatter`, {
5730
+ code: "missing_frontmatter"
5731
+ });
5732
+ }
5733
+ const frontmatter = match[1] ?? "";
5734
+ try {
5735
+ return toStringFields(parseSimpleYaml(frontmatter));
5736
+ } catch (cause) {
5737
+ const detail = cause instanceof Error ? cause.message : String(cause);
5738
+ throw new ConfigurationError(
5739
+ `Skill ${fallbackName} has malformed YAML frontmatter: ${detail}`,
5740
+ { code: "schema_invalid", cause }
5741
+ );
5742
+ }
5743
+ }
5744
+ function resolveName(fields, fallbackName) {
5745
+ if (hasContent(fields.name)) return fields.name;
5746
+ if (hasContent(fallbackName)) return fallbackName;
5747
+ throw new ConfigurationError("Skill at unknown path is missing required field: name", {
5748
+ code: "schema_invalid"
5749
+ });
5750
+ }
5751
+ function ensureRequiredFields(fields, name) {
5752
+ if (!hasContent(fields.description)) {
5753
+ throw new ConfigurationError(`Skill ${name} is missing required field: description`, {
5754
+ code: "schema_invalid"
5755
+ });
5756
+ }
5757
+ }
5758
+ function buildFrontmatter(fields, name) {
5759
+ const description = fields.description;
5760
+ if (description === void 0) {
5761
+ throw new ConfigurationError(`Skill ${name} missing description`, { code: "schema_invalid" });
5762
+ }
5763
+ const result = { name, description };
5764
+ if (hasContent(fields.category)) result.category = fields.category;
5765
+ const deps = parseDependencies(fields.dependencies);
5766
+ if (deps !== void 0) result.dependencies = deps;
5767
+ return result;
5768
+ }
5769
+ function parseDependencies(raw) {
5770
+ if (!hasContent(raw)) return void 0;
5771
+ const deps = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
5772
+ return deps.length > 0 ? deps : void 0;
5773
+ }
5774
+ function hasContent(value) {
5775
+ return value !== void 0 && value.trim().length > 0;
5776
+ }
5777
+ var init_skill_frontmatter = __esm({
5778
+ "src/internal/runtime/skills/skill-frontmatter.ts"() {
5779
+ init_errors();
5780
+ init_yaml_frontmatter();
5781
+ }
5782
+ });
5783
+ async function discoverSkills(dir, options) {
5784
+ let entries;
5785
+ try {
5786
+ entries = await readWorkspaceDir(dir, "skills_read_error", "skills directory");
5787
+ } catch {
5788
+ return [];
5789
+ }
5790
+ const skills = [];
5791
+ for (const entry of entries) {
5792
+ if (!entry.isDirectory()) continue;
5793
+ let skillDir;
5794
+ try {
5795
+ skillDir = safePathJoin(dir, entry.name);
5796
+ assertNoSymlinkEscape(skillDir, dir);
5797
+ } catch {
5798
+ continue;
5799
+ }
5800
+ const skillPath = path.join(skillDir, "SKILL.md");
5801
+ let raw;
5802
+ try {
5803
+ raw = await promises.readFile(skillPath, "utf8");
5804
+ } catch {
5805
+ continue;
5806
+ }
5807
+ const skill = tryParseSkill(raw, entry.name, skillPath, options);
5808
+ if (skill !== void 0) skills.push(skill);
5809
+ }
5810
+ return skills;
5811
+ }
5812
+ function tryParseSkill(raw, fallbackName, source, options) {
5813
+ try {
5814
+ const frontmatter = parseSkillFrontmatter(raw, fallbackName);
5815
+ const skill = {
5816
+ name: frontmatter.name,
5817
+ description: frontmatter.description,
5818
+ source
5819
+ };
5820
+ if (frontmatter.category !== void 0) skill.category = frontmatter.category;
5821
+ if (frontmatter.dependencies !== void 0) skill.dependencies = frontmatter.dependencies;
5822
+ return skill;
5823
+ } catch (cause) {
5824
+ if (cause instanceof ConfigurationError) {
5825
+ options?.onInvalidSkill?.({
5826
+ name: fallbackName,
5827
+ source,
5828
+ code: cause.code ?? "unknown",
5829
+ message: cause.message
5830
+ });
5831
+ return void 0;
5832
+ }
5833
+ throw cause;
5834
+ }
5835
+ }
5836
+ var init_discover_skills = __esm({
5837
+ "src/internal/runtime/skills/discover-skills.ts"() {
5838
+ init_errors();
5839
+ init_path_guard();
5840
+ init_workspace_dir();
5841
+ init_skill_frontmatter();
5842
+ }
5843
+ });
5844
+ var SkillsManager;
5845
+ var init_skills_manager = __esm({
5846
+ "src/internal/runtime/skills/skills-manager.ts"() {
5847
+ init_discover_skills();
5848
+ init_skill_frontmatter();
5849
+ SkillsManager = class {
5850
+ constructor(cwd, _enabled, settingSourcesIncludeProject, skillsDir, inline) {
5851
+ this.cwd = cwd;
5852
+ this.settingSourcesIncludeProject = settingSourcesIncludeProject;
5853
+ this.skillsDir = skillsDir;
5854
+ this.inline = inline;
5855
+ }
5856
+ cwd;
5857
+ settingSourcesIncludeProject;
5858
+ skillsDir;
5859
+ inline;
5860
+ skills = [];
5861
+ async initialize() {
5862
+ if (!this.settingSourcesIncludeProject) {
5863
+ this.skills = this.mergeInline([]);
5864
+ return;
5865
+ }
5866
+ await this.refresh();
5867
+ }
5868
+ async refresh() {
5869
+ const skillsRoot = this.skillsDir ?? path.join(this.cwd, ".theokit", "skills");
5870
+ const discovered = await discoverSkills(skillsRoot, {
5871
+ onInvalidSkill: (info) => {
5872
+ process.stderr.write(
5873
+ `[theokit-sdk] skill ${info.name} skipped (${info.code}): ${info.message}
5874
+ `
5875
+ );
5876
+ }
5877
+ });
5878
+ this.skills = this.mergeInline(discovered);
5879
+ }
5880
+ /** M22 — merge inline skills over discovered ones; inline wins on a name conflict. */
5881
+ mergeInline(discovered) {
5882
+ if (this.inline === void 0 || this.inline.length === 0) return discovered;
5883
+ const inlineNames = new Set(this.inline.map((s) => s.name));
5884
+ return [...discovered.filter((s) => !inlineNames.has(s.name)), ...this.inline];
5885
+ }
5886
+ list() {
5887
+ return Promise.resolve(this.skills);
5888
+ }
5889
+ /**
5890
+ * SE20 — resolve a skill by name INCLUDING its body. Inline (`createSkill`)
5891
+ * skills carry `instructions` on the object; filesystem skills read the body
5892
+ * from their `source` SKILL.md (frontmatter stripped). `undefined` when no
5893
+ * enabled skill matches (malformed skills were already excluded at discovery).
5894
+ */
5895
+ async get(name) {
5896
+ const skill = this.skills.find((s) => s.name === name);
5897
+ if (skill === void 0) return void 0;
5898
+ const instructions = typeof skill.instructions === "string" ? skill.instructions : stripSkillFrontmatter(await promises.readFile(skill.source, "utf8"));
5899
+ const references = skill.references;
5900
+ return {
5901
+ name: skill.name,
5902
+ description: skill.description,
5903
+ instructions,
5904
+ ...references !== void 0 ? { references } : {}
5905
+ };
5906
+ }
5907
+ };
5908
+ }
5909
+ });
5910
+
5696
5911
  // src/internal/runtime/system-prompt/local-assembly.ts
5697
- async function buildSystemPromptContext(inputs, userText, memoryFacts) {
5698
- const skills = inputs.skillsManager !== void 0 ? await inputs.skillsManager.list() : [];
5912
+ async function resolveSendSkills(inputs, userText, memoryFacts) {
5913
+ const skills = inputs.options.skills;
5914
+ if (typeof skills !== "function") {
5915
+ return { manager: inputs.skillsManager, autoInject: skills?.autoInject ?? true };
5916
+ }
5917
+ const settings = await skills({
5918
+ agentId: inputs.agentId,
5919
+ cwd: inputs.workspaceCwd,
5920
+ model: inputs.model,
5921
+ userMessage: userText,
5922
+ memory: memoryFacts.map((fact) => ({ text: fact.text }))
5923
+ });
5924
+ const manager = new SkillsManager(
5925
+ inputs.workspaceCwd,
5926
+ settings.enabled,
5927
+ inputs.settingSourcesIncludeProject,
5928
+ settings.skillsDir,
5929
+ settings.inline
5930
+ );
5931
+ await manager.initialize();
5932
+ return { manager, autoInject: settings.autoInject ?? true };
5933
+ }
5934
+ async function buildSystemPromptContext(inputs, userText, memoryFacts, manager = inputs.skillsManager) {
5935
+ const skills = manager !== void 0 ? await manager.list() : [];
5699
5936
  return {
5700
5937
  agentId: inputs.agentId,
5701
5938
  cwd: inputs.workspaceCwd,
@@ -5706,10 +5943,11 @@ async function buildSystemPromptContext(inputs, userText, memoryFacts) {
5706
5943
  };
5707
5944
  }
5708
5945
  async function buildAssemblyContext(inputs, userText, baseSystemPrompt, memoryFacts, activeMemorySummary) {
5709
- const baseCtx = await buildSystemPromptContext(inputs, userText, memoryFacts);
5946
+ const resolved = await resolveSendSkills(inputs, userText, memoryFacts);
5947
+ const baseCtx = await buildSystemPromptContext(inputs, userText, memoryFacts, resolved.manager);
5710
5948
  const assemblyCtx = {
5711
5949
  ...baseCtx,
5712
- skillsAutoInject: inputs.options.skills?.autoInject ?? true,
5950
+ skillsAutoInject: resolved.autoInject,
5713
5951
  memoryAutoInject: inputs.options.memory?.autoInject ?? true
5714
5952
  };
5715
5953
  if (baseSystemPrompt !== void 0) assemblyCtx.baseSystemPrompt = baseSystemPrompt;
@@ -5735,6 +5973,7 @@ async function assembleSystemPromptForSend(inputs, userText, baseSystemPrompt, m
5735
5973
  }
5736
5974
  var init_local_assembly = __esm({
5737
5975
  "src/internal/runtime/system-prompt/local-assembly.ts"() {
5976
+ init_skills_manager();
5738
5977
  }
5739
5978
  });
5740
5979
 
@@ -7080,187 +7319,6 @@ var init_plugins_manager = __esm({
7080
7319
  }
7081
7320
  });
7082
7321
 
7083
- // src/internal/runtime/skills/skill-frontmatter.ts
7084
- function asString(v) {
7085
- return typeof v === "string" ? v : void 0;
7086
- }
7087
- function toStringFields(raw) {
7088
- const out = {};
7089
- for (const [k, v] of Object.entries(raw)) out[k] = asString(v);
7090
- return out;
7091
- }
7092
- function parseSkillFrontmatter(raw, fallbackName) {
7093
- const fields = extractAndParseFrontmatter(raw, fallbackName);
7094
- const name = resolveName(fields, fallbackName);
7095
- ensureRequiredFields(fields, name);
7096
- return buildFrontmatter(fields, name);
7097
- }
7098
- function extractAndParseFrontmatter(raw, fallbackName) {
7099
- const match = /^---\s*\n([\s\S]*?)\n---\s*\n/.exec(raw);
7100
- if (match === null) {
7101
- throw new ConfigurationError(`Skill ${fallbackName} is missing frontmatter`, {
7102
- code: "missing_frontmatter"
7103
- });
7104
- }
7105
- const frontmatter = match[1] ?? "";
7106
- try {
7107
- return toStringFields(parseSimpleYaml(frontmatter));
7108
- } catch (cause) {
7109
- const detail = cause instanceof Error ? cause.message : String(cause);
7110
- throw new ConfigurationError(
7111
- `Skill ${fallbackName} has malformed YAML frontmatter: ${detail}`,
7112
- { code: "schema_invalid", cause }
7113
- );
7114
- }
7115
- }
7116
- function resolveName(fields, fallbackName) {
7117
- if (hasContent(fields.name)) return fields.name;
7118
- if (hasContent(fallbackName)) return fallbackName;
7119
- throw new ConfigurationError("Skill at unknown path is missing required field: name", {
7120
- code: "schema_invalid"
7121
- });
7122
- }
7123
- function ensureRequiredFields(fields, name) {
7124
- if (!hasContent(fields.description)) {
7125
- throw new ConfigurationError(`Skill ${name} is missing required field: description`, {
7126
- code: "schema_invalid"
7127
- });
7128
- }
7129
- }
7130
- function buildFrontmatter(fields, name) {
7131
- const description = fields.description;
7132
- if (description === void 0) {
7133
- throw new ConfigurationError(`Skill ${name} missing description`, { code: "schema_invalid" });
7134
- }
7135
- const result = { name, description };
7136
- if (hasContent(fields.category)) result.category = fields.category;
7137
- const deps = parseDependencies(fields.dependencies);
7138
- if (deps !== void 0) result.dependencies = deps;
7139
- return result;
7140
- }
7141
- function parseDependencies(raw) {
7142
- if (!hasContent(raw)) return void 0;
7143
- const deps = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7144
- return deps.length > 0 ? deps : void 0;
7145
- }
7146
- function hasContent(value) {
7147
- return value !== void 0 && value.trim().length > 0;
7148
- }
7149
- var init_skill_frontmatter = __esm({
7150
- "src/internal/runtime/skills/skill-frontmatter.ts"() {
7151
- init_errors();
7152
- init_yaml_frontmatter();
7153
- }
7154
- });
7155
- async function discoverSkills(dir, options) {
7156
- let entries;
7157
- try {
7158
- entries = await readWorkspaceDir(dir, "skills_read_error", "skills directory");
7159
- } catch {
7160
- return [];
7161
- }
7162
- const skills = [];
7163
- for (const entry of entries) {
7164
- if (!entry.isDirectory()) continue;
7165
- let skillDir;
7166
- try {
7167
- skillDir = safePathJoin(dir, entry.name);
7168
- assertNoSymlinkEscape(skillDir, dir);
7169
- } catch {
7170
- continue;
7171
- }
7172
- const skillPath = path.join(skillDir, "SKILL.md");
7173
- let raw;
7174
- try {
7175
- raw = await promises.readFile(skillPath, "utf8");
7176
- } catch {
7177
- continue;
7178
- }
7179
- const skill = tryParseSkill(raw, entry.name, skillPath, options);
7180
- if (skill !== void 0) skills.push(skill);
7181
- }
7182
- return skills;
7183
- }
7184
- function tryParseSkill(raw, fallbackName, source, options) {
7185
- try {
7186
- const frontmatter = parseSkillFrontmatter(raw, fallbackName);
7187
- const skill = {
7188
- name: frontmatter.name,
7189
- description: frontmatter.description,
7190
- source
7191
- };
7192
- if (frontmatter.category !== void 0) skill.category = frontmatter.category;
7193
- if (frontmatter.dependencies !== void 0) skill.dependencies = frontmatter.dependencies;
7194
- return skill;
7195
- } catch (cause) {
7196
- if (cause instanceof ConfigurationError) {
7197
- options?.onInvalidSkill?.({
7198
- name: fallbackName,
7199
- source,
7200
- code: cause.code ?? "unknown",
7201
- message: cause.message
7202
- });
7203
- return void 0;
7204
- }
7205
- throw cause;
7206
- }
7207
- }
7208
- var init_discover_skills = __esm({
7209
- "src/internal/runtime/skills/discover-skills.ts"() {
7210
- init_errors();
7211
- init_path_guard();
7212
- init_workspace_dir();
7213
- init_skill_frontmatter();
7214
- }
7215
- });
7216
- var SkillsManager;
7217
- var init_skills_manager = __esm({
7218
- "src/internal/runtime/skills/skills-manager.ts"() {
7219
- init_discover_skills();
7220
- SkillsManager = class {
7221
- constructor(cwd, _enabled, settingSourcesIncludeProject, skillsDir, inline) {
7222
- this.cwd = cwd;
7223
- this.settingSourcesIncludeProject = settingSourcesIncludeProject;
7224
- this.skillsDir = skillsDir;
7225
- this.inline = inline;
7226
- }
7227
- cwd;
7228
- settingSourcesIncludeProject;
7229
- skillsDir;
7230
- inline;
7231
- skills = [];
7232
- async initialize() {
7233
- if (!this.settingSourcesIncludeProject) {
7234
- this.skills = this.mergeInline([]);
7235
- return;
7236
- }
7237
- await this.refresh();
7238
- }
7239
- async refresh() {
7240
- const skillsRoot = this.skillsDir ?? path.join(this.cwd, ".theokit", "skills");
7241
- const discovered = await discoverSkills(skillsRoot, {
7242
- onInvalidSkill: (info) => {
7243
- process.stderr.write(
7244
- `[theokit-sdk] skill ${info.name} skipped (${info.code}): ${info.message}
7245
- `
7246
- );
7247
- }
7248
- });
7249
- this.skills = this.mergeInline(discovered);
7250
- }
7251
- /** M22 — merge inline skills over discovered ones; inline wins on a name conflict. */
7252
- mergeInline(discovered) {
7253
- if (this.inline === void 0 || this.inline.length === 0) return discovered;
7254
- const inlineNames = new Set(this.inline.map((s) => s.name));
7255
- return [...discovered.filter((s) => !inlineNames.has(s.name)), ...this.inline];
7256
- }
7257
- list() {
7258
- return Promise.resolve(this.skills);
7259
- }
7260
- };
7261
- }
7262
- });
7263
-
7264
7322
  // src/internal/runtime/local-agent/local-agent-bootstrap.ts
7265
7323
  function registerLocalAgent(args) {
7266
7324
  registerAgent({
@@ -7296,16 +7354,23 @@ function bootstrapSubmanagers(args) {
7296
7354
  );
7297
7355
  }
7298
7356
  if (args.options.skills !== void 0 || args.settingSourcesIncludeProject) {
7357
+ const staticSkills = typeof args.options.skills === "function" ? void 0 : args.options.skills;
7299
7358
  out.skillsManager = new SkillsManager(
7300
7359
  args.workspaceCwd,
7301
- args.options.skills?.enabled,
7360
+ staticSkills?.enabled,
7302
7361
  args.settingSourcesIncludeProject,
7303
7362
  // M22 — custom skills directory + inline (code-defined) skills.
7304
- args.options.skills?.skillsDir,
7305
- args.options.skills?.inline
7363
+ staticSkills?.skillsDir,
7364
+ staticSkills?.inline
7306
7365
  );
7307
7366
  const localSkills = out.skillsManager;
7308
- out.skills = { list: () => localSkills.list() };
7367
+ out.skills = {
7368
+ // Project to the public shape (name + description only). Inline skills carry
7369
+ // their body + references on the object; `list()` must never leak them —
7370
+ // the body is reachable exclusively through `get()`.
7371
+ list: async () => (await localSkills.list()).map((s) => ({ name: s.name, description: s.description })),
7372
+ get: (name) => localSkills.get(name)
7373
+ };
7309
7374
  }
7310
7375
  if (args.options.plugins !== void 0 || args.settingSourcesIncludePlugins) {
7311
7376
  out.pluginsManager = new PluginsManager(
@@ -17235,7 +17300,7 @@ var init_local_agent = __esm({
17235
17300
  }
17236
17301
  // biome-ignore format: G8 budget — thin accessor for the assembly inputs.
17237
17302
  assemblyInputs() {
17238
- return { agentId: this.agentId, workspaceCwd: this.workspaceCwd, model: this.model, options: this.options, context: this.context, skillsManager: this.skillsManager, systemPromptPipeline: this.systemPromptPipeline };
17303
+ return { agentId: this.agentId, workspaceCwd: this.workspaceCwd, model: this.model, options: this.options, context: this.context, skillsManager: this.skillsManager, settingSourcesIncludeProject: this.settingSourcesIncludeProject, systemPromptPipeline: this.systemPromptPipeline };
17239
17304
  }
17240
17305
  async resolveSystemPrompt(userText, options, memoryFacts) {
17241
17306
  const base = await resolveSystemPromptForSend(