@juspay/neurolink 9.85.1 → 9.86.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/browser/neurolink.min.js +370 -362
  3. package/dist/context/contextCompactor.js +16 -2
  4. package/dist/context/stages/slidingWindowTruncator.js +76 -30
  5. package/dist/core/conversationMemoryManager.js +13 -2
  6. package/dist/core/redisConversationMemoryManager.js +10 -1
  7. package/dist/lib/context/contextCompactor.js +16 -2
  8. package/dist/lib/context/stages/slidingWindowTruncator.js +76 -30
  9. package/dist/lib/core/conversationMemoryManager.js +13 -2
  10. package/dist/lib/core/redisConversationMemoryManager.js +10 -1
  11. package/dist/lib/neurolink.d.ts +31 -6
  12. package/dist/lib/neurolink.js +163 -33
  13. package/dist/lib/skills/skillMatcher.d.ts +33 -4
  14. package/dist/lib/skills/skillMatcher.js +81 -6
  15. package/dist/lib/skills/skillSessionTracker.d.ts +52 -0
  16. package/dist/lib/skills/skillSessionTracker.js +150 -0
  17. package/dist/lib/skills/skillStoreRedis.d.ts +8 -0
  18. package/dist/lib/skills/skillStoreRedis.js +18 -2
  19. package/dist/lib/skills/skillStoreS3.d.ts +17 -2
  20. package/dist/lib/skills/skillStoreS3.js +78 -6
  21. package/dist/lib/skills/skillStores.d.ts +16 -2
  22. package/dist/lib/skills/skillStores.js +94 -5
  23. package/dist/lib/skills/skillTools.d.ts +26 -10
  24. package/dist/lib/skills/skillTools.js +190 -79
  25. package/dist/lib/skills/skillsManager.d.ts +25 -1
  26. package/dist/lib/skills/skillsManager.js +46 -4
  27. package/dist/lib/types/config.d.ts +5 -5
  28. package/dist/lib/types/conversation.d.ts +27 -0
  29. package/dist/lib/types/skills.d.ts +145 -14
  30. package/dist/lib/types/skills.js +3 -3
  31. package/dist/lib/utils/conversationMemory.d.ts +1 -1
  32. package/dist/lib/utils/conversationMemory.js +15 -2
  33. package/dist/neurolink.d.ts +31 -6
  34. package/dist/neurolink.js +163 -33
  35. package/dist/skills/skillMatcher.d.ts +33 -4
  36. package/dist/skills/skillMatcher.js +81 -6
  37. package/dist/skills/skillSessionTracker.d.ts +52 -0
  38. package/dist/skills/skillSessionTracker.js +149 -0
  39. package/dist/skills/skillStoreRedis.d.ts +8 -0
  40. package/dist/skills/skillStoreRedis.js +18 -2
  41. package/dist/skills/skillStoreS3.d.ts +17 -2
  42. package/dist/skills/skillStoreS3.js +78 -6
  43. package/dist/skills/skillStores.d.ts +16 -2
  44. package/dist/skills/skillStores.js +94 -5
  45. package/dist/skills/skillTools.d.ts +26 -10
  46. package/dist/skills/skillTools.js +190 -79
  47. package/dist/skills/skillsManager.d.ts +25 -1
  48. package/dist/skills/skillsManager.js +46 -4
  49. package/dist/types/config.d.ts +5 -5
  50. package/dist/types/conversation.d.ts +27 -0
  51. package/dist/types/skills.d.ts +145 -14
  52. package/dist/types/skills.js +3 -3
  53. package/dist/utils/conversationMemory.d.ts +1 -1
  54. package/dist/utils/conversationMemory.js +15 -2
  55. package/package.json +1 -1
@@ -9,11 +9,19 @@
9
9
  import type { SkillDefinition, SkillIndexItem, SkillRedisStorageConfig, SkillStore } from "../types/index.js";
10
10
  export declare class RedisSkillStore implements SkillStore {
11
11
  private readonly keyPrefix;
12
+ /**
13
+ * Resources live under `<keyPrefix>__resources__:` — a reserved segment
14
+ * inside the skill prefix, explicitly excluded from the index SCAN so
15
+ * resource values are never mistaken for skills, whatever the
16
+ * configured keyPrefix looks like.
17
+ */
18
+ private readonly resourcePrefix;
12
19
  private readonly normalizedConfig;
13
20
  private clientPromise;
14
21
  constructor(config: SkillRedisStorageConfig);
15
22
  private getClient;
16
23
  private skillKey;
24
+ getResource(id: string, resourcePath: string): Promise<string | null>;
17
25
  get(id: string): Promise<SkillDefinition | null>;
18
26
  put(skill: SkillDefinition): Promise<void>;
19
27
  delete(id: string): Promise<void>;
@@ -8,14 +8,22 @@
8
8
  */
9
9
  import { getNormalizedConfig, getPooledRedisClient, scanKeys, } from "../utils/redis.js";
10
10
  import { logger } from "../utils/logger.js";
11
- import { toSkillIndexItem } from "./skillMatcher.js";
11
+ import { isSafeSkillResourcePath, toSkillIndexItem } from "./skillMatcher.js";
12
12
  const DEFAULT_KEY_PREFIX = "neurolink:skills:";
13
13
  export class RedisSkillStore {
14
14
  keyPrefix;
15
+ /**
16
+ * Resources live under `<keyPrefix>__resources__:` — a reserved segment
17
+ * inside the skill prefix, explicitly excluded from the index SCAN so
18
+ * resource values are never mistaken for skills, whatever the
19
+ * configured keyPrefix looks like.
20
+ */
21
+ resourcePrefix;
15
22
  normalizedConfig;
16
23
  clientPromise = null;
17
24
  constructor(config) {
18
25
  this.keyPrefix = config.keyPrefix ?? DEFAULT_KEY_PREFIX;
26
+ this.resourcePrefix = `${this.keyPrefix}__resources__:`;
19
27
  this.normalizedConfig = getNormalizedConfig({
20
28
  ...(config.url ? { url: config.url } : {}),
21
29
  ...(config.host ? { host: config.host } : {}),
@@ -39,6 +47,14 @@ export class RedisSkillStore {
39
47
  skillKey(id) {
40
48
  return `${this.keyPrefix}${id}`;
41
49
  }
50
+ async getResource(id, resourcePath) {
51
+ if (!isSafeSkillResourcePath(resourcePath)) {
52
+ return null;
53
+ }
54
+ const client = await this.getClient();
55
+ const raw = await client.get(`${this.resourcePrefix}${id}:${resourcePath}`);
56
+ return raw ? String(raw) : null;
57
+ }
42
58
  async get(id) {
43
59
  const client = await this.getClient();
44
60
  const raw = await client.get(this.skillKey(id));
@@ -55,7 +71,7 @@ export class RedisSkillStore {
55
71
  }
56
72
  async index() {
57
73
  const client = await this.getClient();
58
- const keys = await scanKeys(client, `${this.keyPrefix}*`);
74
+ const keys = (await scanKeys(client, `${this.keyPrefix}*`)).filter((key) => !key.startsWith(this.resourcePrefix));
59
75
  if (keys.length === 0) {
60
76
  return [];
61
77
  }
@@ -22,20 +22,35 @@ export declare class S3SkillStore implements SkillStore {
22
22
  private readonly injectedOps?;
23
23
  private readonly prefix;
24
24
  private ops;
25
+ /** Last parsed index + its ETag, revalidated with If-None-Match reads. */
26
+ private cachedIndexDoc;
27
+ private cachedIndexEtag;
25
28
  constructor(config: SkillS3StorageConfig,
26
29
  /** Test/host seam — omit to build ops from @aws-sdk/client-s3 lazily. */
27
30
  injectedOps?: SkillS3ObjectOps | undefined);
31
+ invalidate(): void;
28
32
  private getOps;
29
33
  private skillKey;
30
34
  private indexKey;
35
+ /** Resources live beside the skill object: <prefix>skills/<id>/<path>. */
36
+ private resourceKey;
37
+ getResource(id: string, resourcePath: string): Promise<string | null>;
31
38
  get(id: string): Promise<SkillDefinition | null>;
32
39
  put(skill: SkillDefinition): Promise<void>;
33
40
  delete(id: string): Promise<void>;
34
41
  index(): Promise<SkillIndexItem[]>;
35
42
  private writeIndex;
36
43
  /**
37
- * Read index.json; when missing or unparsable, rebuild it from a listing
38
- * of the skills/ prefix and persist the rebuilt document (self-heal).
44
+ * Raw index.json read. Returns the body (with its ETag when available),
45
+ * null body when the object is absent, or notModified when a
46
+ * conditional read reported the cached copy unchanged.
47
+ */
48
+ private readIndexObject;
49
+ /**
50
+ * Read index.json (ETag-revalidated when the ops support conditional
51
+ * reads — an unchanged index costs a 304, not a download); when missing
52
+ * or unparsable, rebuild it from a listing of the skills/ prefix and
53
+ * persist the rebuilt document (self-heal).
39
54
  */
40
55
  private readOrRebuildIndex;
41
56
  }
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import { createRequire } from "node:module";
19
19
  import { logger } from "../utils/logger.js";
20
- import { toSkillIndexItem } from "./skillMatcher.js";
20
+ import { isSafeSkillResourcePath, toSkillIndexItem } from "./skillMatcher.js";
21
21
  const lazyRequire = createRequire(import.meta.url);
22
22
  let cachedS3Module;
23
23
  function loadS3Module() {
@@ -97,6 +97,32 @@ function createDefaultOps(config) {
97
97
  } while (continuationToken);
98
98
  return keys;
99
99
  },
100
+ async getObjectConditional(key, etag) {
101
+ try {
102
+ const response = (await client.send(new mod.GetObjectCommand({
103
+ Bucket: bucket,
104
+ Key: key,
105
+ ...(etag ? { IfNoneMatch: etag } : {}),
106
+ })));
107
+ const body = (await response.Body?.transformToString()) ?? null;
108
+ return {
109
+ body,
110
+ ...(response.ETag ? { etag: response.ETag } : {}),
111
+ };
112
+ }
113
+ catch (error) {
114
+ const name = error.name;
115
+ const status = error
116
+ .$metadata?.httpStatusCode;
117
+ if (status === 304 || name === "304" || name === "NotModified") {
118
+ return { body: null, notModified: true };
119
+ }
120
+ if (name === "NoSuchKey" || name === "NotFound") {
121
+ return { body: null };
122
+ }
123
+ throw error;
124
+ }
125
+ },
100
126
  };
101
127
  }
102
128
  export class S3SkillStore {
@@ -104,6 +130,9 @@ export class S3SkillStore {
104
130
  injectedOps;
105
131
  prefix;
106
132
  ops = null;
133
+ /** Last parsed index + its ETag, revalidated with If-None-Match reads. */
134
+ cachedIndexDoc = null;
135
+ cachedIndexEtag;
107
136
  constructor(config,
108
137
  /** Test/host seam — omit to build ops from @aws-sdk/client-s3 lazily. */
109
138
  injectedOps) {
@@ -113,6 +142,10 @@ export class S3SkillStore {
113
142
  this.prefix =
114
143
  rawPrefix === "" || rawPrefix.endsWith("/") ? rawPrefix : `${rawPrefix}/`;
115
144
  }
145
+ invalidate() {
146
+ this.cachedIndexDoc = null;
147
+ this.cachedIndexEtag = undefined;
148
+ }
116
149
  getOps() {
117
150
  if (!this.ops) {
118
151
  this.ops = this.injectedOps ?? createDefaultOps(this.config);
@@ -125,6 +158,16 @@ export class S3SkillStore {
125
158
  indexKey() {
126
159
  return `${this.prefix}index.json`;
127
160
  }
161
+ /** Resources live beside the skill object: <prefix>skills/<id>/<path>. */
162
+ resourceKey(id, resourcePath) {
163
+ return `${this.prefix}skills/${id}/${resourcePath}`;
164
+ }
165
+ async getResource(id, resourcePath) {
166
+ if (!isSafeSkillResourcePath(resourcePath)) {
167
+ return null;
168
+ }
169
+ return this.getOps().getObject(this.resourceKey(id, resourcePath));
170
+ }
128
171
  async get(id) {
129
172
  const body = await this.getOps().getObject(this.skillKey(id));
130
173
  if (!body) {
@@ -169,18 +212,42 @@ export class S3SkillStore {
169
212
  async writeIndex(index) {
170
213
  index.lastUpdated = new Date().toISOString();
171
214
  await this.getOps().putObject(this.indexKey(), JSON.stringify(index, null, 2));
215
+ // The write changed the object's ETag; keep the doc, revalidate next read.
216
+ this.cachedIndexDoc = index;
217
+ this.cachedIndexEtag = undefined;
172
218
  }
173
219
  /**
174
- * Read index.json; when missing or unparsable, rebuild it from a listing
175
- * of the skills/ prefix and persist the rebuilt document (self-heal).
220
+ * Raw index.json read. Returns the body (with its ETag when available),
221
+ * null body when the object is absent, or notModified when a
222
+ * conditional read reported the cached copy unchanged.
223
+ */
224
+ async readIndexObject(ops) {
225
+ if (ops.getObjectConditional) {
226
+ return ops.getObjectConditional(this.indexKey(), this.cachedIndexDoc ? this.cachedIndexEtag : undefined);
227
+ }
228
+ return { body: await ops.getObject(this.indexKey()) };
229
+ }
230
+ /**
231
+ * Read index.json (ETag-revalidated when the ops support conditional
232
+ * reads — an unchanged index costs a 304, not a download); when missing
233
+ * or unparsable, rebuild it from a listing of the skills/ prefix and
234
+ * persist the rebuilt document (self-heal).
176
235
  */
177
236
  async readOrRebuildIndex() {
178
237
  const ops = this.getOps();
179
- const raw = await ops.getObject(this.indexKey());
180
- if (raw) {
238
+ const read = await this.readIndexObject(ops);
239
+ if (read.notModified && this.cachedIndexDoc) {
240
+ return this.cachedIndexDoc;
241
+ }
242
+ if (read.body) {
181
243
  try {
182
- const parsed = JSON.parse(raw);
244
+ const parsed = JSON.parse(read.body);
183
245
  if (Array.isArray(parsed.skills)) {
246
+ this.cachedIndexDoc = parsed;
247
+ // Cache the ETag only for a body that parsed: pairing a corrupt
248
+ // object's ETag with the previous good doc would make every
249
+ // later conditional read 304 into permanently stale data.
250
+ this.cachedIndexEtag = read.etag;
184
251
  return parsed;
185
252
  }
186
253
  }
@@ -189,6 +256,7 @@ export class S3SkillStore {
189
256
  error: error instanceof Error ? error.message : String(error),
190
257
  });
191
258
  }
259
+ this.cachedIndexEtag = undefined;
192
260
  }
193
261
  const skillsPrefix = `${this.prefix}skills/`;
194
262
  const keys = await ops.listKeys(skillsPrefix);
@@ -214,6 +282,10 @@ export class S3SkillStore {
214
282
  lastUpdated: new Date().toISOString(),
215
283
  skills,
216
284
  };
285
+ // Cache the rebuilt doc even when persistence below fails, so reads
286
+ // don't fall back to a stale pre-rebuild copy.
287
+ this.cachedIndexDoc = rebuilt;
288
+ this.cachedIndexEtag = undefined;
217
289
  try {
218
290
  await this.writeIndex(rebuilt);
219
291
  logger.info("[SkillStoreS3] Rebuilt skills index", {
@@ -11,26 +11,40 @@ import type { SkillDefinition, SkillIndexItem, SkillStore, SkillsStorageConfig }
11
11
  /** In-process store backed by a Map. */
12
12
  export declare class InMemorySkillStore implements SkillStore {
13
13
  private skills;
14
- constructor(seed?: SkillDefinition[]);
14
+ /** skill id → relative path → content. */
15
+ private resources;
16
+ constructor(seed?: SkillDefinition[], resources?: Record<string, Record<string, string>>);
15
17
  get(id: string): Promise<SkillDefinition | null>;
16
18
  put(skill: SkillDefinition): Promise<void>;
17
19
  delete(id: string): Promise<void>;
18
20
  index(): Promise<SkillIndexItem[]>;
21
+ getResource(id: string, resourcePath: string): Promise<string | null>;
19
22
  }
20
23
  /**
21
24
  * Directory-backed store. Read layouts:
22
25
  * - `<dir>/<id>.json` — JSON SkillDefinition (mutable)
23
26
  * - `<dir>/<name>.md` — frontmatter markdown (read-only source)
24
- * - `<dir>/<name>/SKILL.md` — Claude-skills directory layout (read-only source)
27
+ * - `<dir>/<name>/SKILL.md` — Claude-skills directory layout (read-only source);
28
+ * sibling files become on-demand resources (read_skill_resource)
25
29
  * Mutations always write `<id>.json`; a JSON file shadows a markdown skill
26
30
  * with the same id, so updating a markdown-sourced skill "copies up" to JSON.
27
31
  */
28
32
  export declare class FileSystemSkillStore implements SkillStore {
29
33
  private readonly baseDir;
30
34
  private cache;
35
+ /** skill id → directory, for skills loaded from the SKILL.md layout. */
36
+ private skillDirs;
31
37
  constructor(baseDir: string);
32
38
  invalidate(): void;
33
39
  get(id: string): Promise<SkillDefinition | null>;
40
+ /**
41
+ * Resources exist only for directory-layout skills (`<name>/SKILL.md`) —
42
+ * every sibling file of SKILL.md is addressable by its relative path.
43
+ * The REAL path must stay inside the skill directory: lexical
44
+ * containment alone would follow a symlink planted inside the skill dir
45
+ * to anywhere on the host.
46
+ */
47
+ getResource(id: string, resourcePath: string): Promise<string | null>;
34
48
  put(skill: SkillDefinition): Promise<void>;
35
49
  delete(id: string): Promise<void>;
36
50
  index(): Promise<SkillIndexItem[]>;
@@ -40,9 +40,23 @@ function isValidSkill(candidate) {
40
40
  /** In-process store backed by a Map. */
41
41
  export class InMemorySkillStore {
42
42
  skills = new Map();
43
- constructor(seed) {
43
+ /** skill id → relative path → content. */
44
+ resources = new Map();
45
+ constructor(seed, resources) {
46
+ for (const [skillId, files] of Object.entries(resources ?? {})) {
47
+ this.resources.set(skillId, new Map(Object.entries(files)));
48
+ }
44
49
  for (const skill of seed ?? []) {
45
- this.skills.set(skill.id, normalizeSkill(skill));
50
+ const files = this.resources.get(skill.id);
51
+ const withRefs = files && !skill.resources
52
+ ? {
53
+ ...skill,
54
+ resources: Array.from(files.keys())
55
+ .sort()
56
+ .map((p) => ({ path: p })),
57
+ }
58
+ : skill;
59
+ this.skills.set(skill.id, normalizeSkill(withRefs));
46
60
  }
47
61
  }
48
62
  async get(id) {
@@ -57,6 +71,9 @@ export class InMemorySkillStore {
57
71
  async index() {
58
72
  return Array.from(this.skills.values()).map(toSkillIndexItem);
59
73
  }
74
+ async getResource(id, resourcePath) {
75
+ return this.resources.get(id)?.get(resourcePath) ?? null;
76
+ }
60
77
  }
61
78
  /**
62
79
  * Parse a markdown document with optional YAML frontmatter into a skill.
@@ -112,17 +129,56 @@ function parseSkillMarkdown(raw, fallbackId) {
112
129
  });
113
130
  return candidate;
114
131
  }
132
+ /**
133
+ * List every file bundled beside a skill's SKILL.md as a resource ref,
134
+ * relative paths, sorted for deterministic listings. Recurses into
135
+ * subdirectories (references/, assets/, …).
136
+ */
137
+ function listSkillResourceFiles(skillDir) {
138
+ const refs = [];
139
+ const walk = (current, relative) => {
140
+ let entries;
141
+ try {
142
+ entries = fs.readdirSync(current, { withFileTypes: true });
143
+ }
144
+ catch {
145
+ return;
146
+ }
147
+ for (const entry of entries) {
148
+ const relPath = relative ? `${relative}/${entry.name}` : entry.name;
149
+ if (entry.isDirectory()) {
150
+ walk(path.join(current, entry.name), relPath);
151
+ }
152
+ else if (relPath !== "SKILL.md") {
153
+ try {
154
+ refs.push({
155
+ path: relPath,
156
+ size: fs.statSync(path.join(current, entry.name)).size,
157
+ });
158
+ }
159
+ catch {
160
+ refs.push({ path: relPath });
161
+ }
162
+ }
163
+ }
164
+ };
165
+ walk(skillDir, "");
166
+ return refs.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
167
+ }
115
168
  /**
116
169
  * Directory-backed store. Read layouts:
117
170
  * - `<dir>/<id>.json` — JSON SkillDefinition (mutable)
118
171
  * - `<dir>/<name>.md` — frontmatter markdown (read-only source)
119
- * - `<dir>/<name>/SKILL.md` — Claude-skills directory layout (read-only source)
172
+ * - `<dir>/<name>/SKILL.md` — Claude-skills directory layout (read-only source);
173
+ * sibling files become on-demand resources (read_skill_resource)
120
174
  * Mutations always write `<id>.json`; a JSON file shadows a markdown skill
121
175
  * with the same id, so updating a markdown-sourced skill "copies up" to JSON.
122
176
  */
123
177
  export class FileSystemSkillStore {
124
178
  baseDir;
125
179
  cache = null;
180
+ /** skill id → directory, for skills loaded from the SKILL.md layout. */
181
+ skillDirs = new Map();
126
182
  constructor(baseDir) {
127
183
  this.baseDir = baseDir;
128
184
  }
@@ -133,6 +189,36 @@ export class FileSystemSkillStore {
133
189
  const all = this.load();
134
190
  return all.get(id) ?? null;
135
191
  }
192
+ /**
193
+ * Resources exist only for directory-layout skills (`<name>/SKILL.md`) —
194
+ * every sibling file of SKILL.md is addressable by its relative path.
195
+ * The REAL path must stay inside the skill directory: lexical
196
+ * containment alone would follow a symlink planted inside the skill dir
197
+ * to anywhere on the host.
198
+ */
199
+ async getResource(id, resourcePath) {
200
+ this.load();
201
+ const dir = this.skillDirs.get(id);
202
+ if (!dir) {
203
+ return null;
204
+ }
205
+ try {
206
+ const realDir = fs.realpathSync(dir);
207
+ const realResolved = fs.realpathSync(path.resolve(dir, resourcePath));
208
+ if (realResolved !== realDir &&
209
+ !realResolved.startsWith(realDir + path.sep)) {
210
+ return null;
211
+ }
212
+ return fs.readFileSync(realResolved, "utf-8");
213
+ }
214
+ catch (error) {
215
+ const code = error.code;
216
+ if (code === "ENOENT" || code === "EISDIR" || code === "ELOOP") {
217
+ return null;
218
+ }
219
+ throw error;
220
+ }
221
+ }
136
222
  async put(skill) {
137
223
  fs.mkdirSync(this.baseDir, { recursive: true });
138
224
  const filePath = path.join(this.baseDir, `${skill.id}.json`);
@@ -164,6 +250,7 @@ export class FileSystemSkillStore {
164
250
  return this.cache;
165
251
  }
166
252
  const skills = new Map();
253
+ this.skillDirs = new Map();
167
254
  let entries;
168
255
  try {
169
256
  entries = fs.readdirSync(this.baseDir, { withFileTypes: true });
@@ -188,7 +275,9 @@ export class FileSystemSkillStore {
188
275
  if (fs.existsSync(skillMd)) {
189
276
  const parsed = parseSkillMarkdown(fs.readFileSync(skillMd, "utf-8"), entry.name);
190
277
  if (parsed) {
191
- skills.set(parsed.id, parsed);
278
+ const resources = listSkillResourceFiles(fullPath);
279
+ skills.set(parsed.id, resources.length > 0 ? { ...parsed, resources } : parsed);
280
+ this.skillDirs.set(parsed.id, path.resolve(fullPath));
192
281
  }
193
282
  }
194
283
  }
@@ -236,7 +325,7 @@ export class FileSystemSkillStore {
236
325
  /** Resolve a storage config to a concrete store. Defaults to memory. */
237
326
  export function createSkillStore(config) {
238
327
  if (!config || config.type === "memory") {
239
- return new InMemorySkillStore(config?.type === "memory" ? config.skills : undefined);
328
+ return new InMemorySkillStore(config?.type === "memory" ? config.skills : undefined, config?.type === "memory" ? config.resources : undefined);
240
329
  }
241
330
  if (config.type === "filesystem") {
242
331
  return new FileSystemSkillStore(config.path);
@@ -1,19 +1,35 @@
1
1
  /**
2
- * Built-in skill tools, following the createMemoryRetrievalTools /
3
- * createFileTools / createTaskTools factory pattern.
2
+ * Built-in skill tools.
3
+ *
4
+ * Two factories, mirroring the two disclosure levels:
5
+ *
6
+ * createSkillTools — instance tools registered once at construction:
7
+ * list_skills (lightweight catalog) plus the gated skill_create /
8
+ * skill_update / skill_delete mutation tools.
9
+ *
10
+ * createSkillCallTools — per-call tools injected into options.tools by
11
+ * prepareGenerate/prepareStream (the RAG-tool pattern): use_skill, whose
12
+ * description embeds the `<available_skills>` listing so the model
13
+ * decides from context when a skill applies (no mandatory pre-flight
14
+ * call), and read_skill_resource for on-demand auxiliary files. The
15
+ * sessionId rides in by closure, so activations pin to the session
16
+ * without runtime tool-context plumbing.
4
17
  *
5
18
  * The manager is resolved lazily via a resolver callback so tools can be
6
19
  * registered at construction time while the store initializes on first
7
20
  * use (mirrors how retrieve_context resolves conversationMemory lazily).
8
- *
9
- * Tool descriptions are ported from curator's production skills tools —
10
- * the "always check skills first / no_match is expected, not an error /
11
- * pick the best of 2-5 or ask" prompt engineering is proven in prod.
12
21
  */
13
- import type { SkillsManagerLike, SkillToolsOptions, Tool } from "../types/index.js";
22
+ import type { SkillCallToolsContext, SkillsManagerLike, SkillToolsOptions, Tool } from "../types/index.js";
23
+ /**
24
+ * Per-call skill tools: use_skill + read_skill_resource. Injected into
25
+ * options.tools for one generate()/stream() call; same-name entries shadow
26
+ * any registered tool, so the description always reflects this call's
27
+ * listing and scope.
28
+ */
29
+ export declare function createSkillCallTools(resolveManager: () => SkillsManagerLike | null, context: SkillCallToolsContext): Record<string, Tool>;
14
30
  /**
15
- * Create the built-in skill tools bound to a lazily-resolved manager.
16
- * Returns Vercel AI SDK tool() objects (description + Zod inputSchema +
17
- * execute) keyed by tool name.
31
+ * Instance skill tools bound to a lazily-resolved manager: list_skills
32
+ * plus the gated mutation tools. Returns Vercel AI SDK tool() objects
33
+ * (description + Zod inputSchema + execute) keyed by tool name.
18
34
  */
19
35
  export declare function createSkillTools(resolveManager: () => SkillsManagerLike | null, options?: SkillToolsOptions): Record<string, Tool>;