@juspay/neurolink 9.85.0 → 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 (61) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/browser/neurolink.min.js +420 -412
  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/server/routes/claudeProxyRoutes.d.ts +39 -1
  14. package/dist/lib/server/routes/claudeProxyRoutes.js +300 -41
  15. package/dist/lib/skills/skillMatcher.d.ts +33 -4
  16. package/dist/lib/skills/skillMatcher.js +81 -6
  17. package/dist/lib/skills/skillSessionTracker.d.ts +52 -0
  18. package/dist/lib/skills/skillSessionTracker.js +150 -0
  19. package/dist/lib/skills/skillStoreRedis.d.ts +8 -0
  20. package/dist/lib/skills/skillStoreRedis.js +18 -2
  21. package/dist/lib/skills/skillStoreS3.d.ts +17 -2
  22. package/dist/lib/skills/skillStoreS3.js +78 -6
  23. package/dist/lib/skills/skillStores.d.ts +16 -2
  24. package/dist/lib/skills/skillStores.js +94 -5
  25. package/dist/lib/skills/skillTools.d.ts +26 -10
  26. package/dist/lib/skills/skillTools.js +190 -79
  27. package/dist/lib/skills/skillsManager.d.ts +25 -1
  28. package/dist/lib/skills/skillsManager.js +46 -4
  29. package/dist/lib/types/config.d.ts +5 -5
  30. package/dist/lib/types/conversation.d.ts +27 -0
  31. package/dist/lib/types/proxy.d.ts +30 -2
  32. package/dist/lib/types/skills.d.ts +145 -14
  33. package/dist/lib/types/skills.js +3 -3
  34. package/dist/lib/utils/conversationMemory.d.ts +1 -1
  35. package/dist/lib/utils/conversationMemory.js +15 -2
  36. package/dist/neurolink.d.ts +31 -6
  37. package/dist/neurolink.js +163 -33
  38. package/dist/server/routes/claudeProxyRoutes.d.ts +39 -1
  39. package/dist/server/routes/claudeProxyRoutes.js +300 -41
  40. package/dist/skills/skillMatcher.d.ts +33 -4
  41. package/dist/skills/skillMatcher.js +81 -6
  42. package/dist/skills/skillSessionTracker.d.ts +52 -0
  43. package/dist/skills/skillSessionTracker.js +149 -0
  44. package/dist/skills/skillStoreRedis.d.ts +8 -0
  45. package/dist/skills/skillStoreRedis.js +18 -2
  46. package/dist/skills/skillStoreS3.d.ts +17 -2
  47. package/dist/skills/skillStoreS3.js +78 -6
  48. package/dist/skills/skillStores.d.ts +16 -2
  49. package/dist/skills/skillStores.js +94 -5
  50. package/dist/skills/skillTools.d.ts +26 -10
  51. package/dist/skills/skillTools.js +190 -79
  52. package/dist/skills/skillsManager.d.ts +25 -1
  53. package/dist/skills/skillsManager.js +46 -4
  54. package/dist/types/config.d.ts +5 -5
  55. package/dist/types/conversation.d.ts +27 -0
  56. package/dist/types/proxy.d.ts +30 -2
  57. package/dist/types/skills.d.ts +145 -14
  58. package/dist/types/skills.js +3 -3
  59. package/dist/utils/conversationMemory.d.ts +1 -1
  60. package/dist/utils/conversationMemory.js +15 -2
  61. package/package.json +1 -1
@@ -8,11 +8,13 @@
8
8
  */
9
9
  import { randomUUID } from "node:crypto";
10
10
  import { logger } from "../utils/logger.js";
11
- import { filterSkillIndex, formatSkillsPromptIndex } from "./skillMatcher.js";
11
+ import { filterSkillIndex, formatSkillsPromptIndex, renderSkillListing, sortSkillIndex, } from "./skillMatcher.js";
12
+ import { SkillSessionTracker } from "./skillSessionTracker.js";
12
13
  import { createSkillStore } from "./skillStores.js";
13
14
  const DEFAULT_MAX_MATCHES = 5;
14
15
  const DEFAULT_PROMPT_INDEX_MAX_ITEMS = 50;
15
16
  const DEFAULT_INDEX_CACHE_TTL_MS = 30_000;
17
+ const DEFAULT_LISTING_BUDGET_CHARS = 15_000;
16
18
  export class SkillsManager {
17
19
  config;
18
20
  store;
@@ -20,11 +22,17 @@ export class SkillsManager {
20
22
  cachedIndexAt = 0;
21
23
  /** Serializes writes within this process — see requestMutation(). */
22
24
  mutationQueue = Promise.resolve();
25
+ /** Per-session activation state (pinned skills). */
26
+ sessions = new SkillSessionTracker();
23
27
  constructor(config) {
24
28
  this.config = config;
25
29
  this.store = createSkillStore(config.storage);
26
30
  }
27
- /** Cached index read. TTL 0 disables caching. */
31
+ /**
32
+ * Cached index read, sorted by name. TTL 0 disables caching. Sorting
33
+ * here (not per render) keeps every downstream listing byte-stable
34
+ * regardless of store enumeration order.
35
+ */
28
36
  async getIndex(forceRefresh = false) {
29
37
  const ttl = this.config.indexCacheTtlMs ?? DEFAULT_INDEX_CACHE_TTL_MS;
30
38
  const fresh = this.cachedIndex !== null &&
@@ -33,7 +41,7 @@ export class SkillsManager {
33
41
  if (fresh && !forceRefresh && this.cachedIndex) {
34
42
  return this.cachedIndex;
35
43
  }
36
- const index = await this.store.index();
44
+ const index = sortSkillIndex(await this.store.index());
37
45
  this.cachedIndex = index;
38
46
  this.cachedIndexAt = Date.now();
39
47
  return index;
@@ -87,12 +95,46 @@ export class SkillsManager {
87
95
  * nothing is visible. Never includes instructions.
88
96
  */
89
97
  async buildPromptIndex(options) {
98
+ const items = await this.visibleItems(options);
99
+ return formatSkillsPromptIndex(items, this.config.promptIndexMaxItems ?? DEFAULT_PROMPT_INDEX_MAX_ITEMS);
100
+ }
101
+ /**
102
+ * Render the `<available_skills>` block for the use_skill tool
103
+ * description ("tool" discovery mode), or null when nothing is visible.
104
+ * Bounded by listingBudgetChars; entries are never dropped.
105
+ */
106
+ async buildToolListing(options) {
107
+ const items = await this.visibleItems(options);
108
+ return renderSkillListing(items, this.config.listingBudgetChars ?? DEFAULT_LISTING_BUDGET_CHARS);
109
+ }
110
+ /** Visible (active, scope- and tag-filtered) index entries for one call. */
111
+ async visibleItems(options) {
90
112
  let items = await this.list(options?.scopeId);
91
113
  if (options?.tags && options.tags.length > 0) {
92
114
  const wanted = options.tags.map((t) => t.toLowerCase());
93
115
  items = items.filter((item) => (item.tags ?? []).some((t) => wanted.includes(t.toLowerCase())));
94
116
  }
95
- return formatSkillsPromptIndex(items, this.config.promptIndexMaxItems ?? DEFAULT_PROMPT_INDEX_MAX_ITEMS);
117
+ return items;
118
+ }
119
+ /**
120
+ * Read an auxiliary resource file bundled with a skill. Paths are
121
+ * relative to the skill; traversal segments are rejected. Null when the
122
+ * skill, the resource, or store resource support is absent.
123
+ */
124
+ async getResource(idOrName, resourcePath) {
125
+ const normalized = resourcePath.replace(/\\/g, "/");
126
+ if (normalized.startsWith("/") ||
127
+ normalized.split("/").some((segment) => segment === "..")) {
128
+ throw new Error(`Invalid resource path "${resourcePath}" — must be relative to the skill directory`);
129
+ }
130
+ if (!this.store.getResource) {
131
+ return null;
132
+ }
133
+ const skill = await this.get(idOrName);
134
+ if (!skill) {
135
+ return null;
136
+ }
137
+ return this.store.getResource(skill.id, normalized);
96
138
  }
97
139
  /**
98
140
  * Whether skill create/update/delete is enabled on this instance. Gates the
@@ -117,11 +117,11 @@ export type NeurolinkConstructorConfig = {
117
117
  classifierRouter?: ClassifierRouterConfig;
118
118
  /**
119
119
  * Native skills: versioned, discoverable instruction packs (SOPs,
120
- * playbooks) with progressive disclosure. When enabled, built-in
121
- * search_skills / list_skills tools are registered (plus gated mutation
122
- * tools) and a compact skills index is injected into the system prompt
123
- * of each generate()/stream() call. Opt-in and fails open on read paths.
124
- * See {@link SkillsConfig}.
120
+ * playbooks) with progressive disclosure. When enabled, each
121
+ * generate()/stream() call gets a skills discovery listing plus
122
+ * use_skill / read_skill_resource tools; activated skill instructions
123
+ * pin to the session so they are loaded once and replayed from history.
124
+ * Opt-in and fails open on read paths. See {@link SkillsConfig}.
125
125
  */
126
126
  skills?: SkillsConfig;
127
127
  };
@@ -250,6 +250,20 @@ export type ChatMessageMetadata = {
250
250
  * payload from the local artifact store.
251
251
  */
252
252
  artifactId?: string;
253
+ /**
254
+ * Marks a pinned skill-activation message: the full instructions of a
255
+ * skill loaded via use_skill, persisted into session history so later
256
+ * turns replay it verbatim instead of re-fetching the skill. Pinned
257
+ * skill messages are protected from sliding-window truncation and are
258
+ * re-included after memory summarization.
259
+ */
260
+ isSkill?: boolean;
261
+ /** Skill id of a pinned skill-activation message. */
262
+ skillId?: string;
263
+ /** Skill name of a pinned skill-activation message. */
264
+ skillName?: string;
265
+ /** Skill version captured at activation (sessions pin the activated version). */
266
+ skillVersion?: number;
253
267
  };
254
268
  /**
255
269
  * Chat message format for conversation history
@@ -383,6 +397,19 @@ export type StoreConversationTurnOptions = {
383
397
  };
384
398
  /** Gemini 3 thought signature for reasoning continuity across turns */
385
399
  thoughtSignature?: string;
400
+ /**
401
+ * Pinned skill-activation messages (skills v2) recorded during this turn.
402
+ * Inserted between the user and assistant messages so replayed history
403
+ * mirrors the actual order: ask → skill loaded → answer. Stored verbatim —
404
+ * skill instructions are never truncated.
405
+ *
406
+ * Invariant for history consumers: a skill-bearing turn is a
407
+ * user → skill(user-role, metadata.isSkill) → assistant triplet, so
408
+ * stored history is NOT strictly pair-wise alternating. Pair-based
409
+ * logic must filter `metadata.isSkill` first (see slidingWindowTruncator
410
+ * for the canonical partition-and-reanchor pattern).
411
+ */
412
+ skillMessages?: ChatMessage[];
386
413
  };
387
414
  /**
388
415
  * Lightweight session metadata for efficient session listing
@@ -574,11 +574,26 @@ export type PreparedAnthropicAccountAttempt = {
574
574
  fetchStartMs?: number;
575
575
  upstreamSpan?: Span;
576
576
  };
577
+ /** How to cool an account after a genuine (non-anti-abuse) 429, derived from
578
+ * the response's quota headers + retry-after. */
579
+ export type AccountCooldownPlan = {
580
+ reason: AccountCoolingReason;
581
+ /** Epoch-ms until which the account should not be used. */
582
+ coolingUntil: number;
583
+ /** When true (5h/7d window rejected), rotate immediately — retrying the same
584
+ * account is futile until its window resets. When false (transient burst),
585
+ * a small number of jittered same-account retries is allowed first. */
586
+ rotateImmediately: boolean;
587
+ };
577
588
  export type AnthropicUpstreamFetchResult = {
578
589
  continueLoop: boolean;
579
590
  retrySameAccount?: boolean;
580
591
  /** When set, the caller should wait this many ms before retrying (from upstream retry-after). */
581
592
  retryAfterMs?: number;
593
+ /** Set on a genuine 429: how long / why to cool this account before rotating. */
594
+ cooldownPlan?: AccountCooldownPlan;
595
+ /** Quota snapshot parsed from the response headers (429 or success), if present. */
596
+ quota?: AccountQuota;
582
597
  response?: Response;
583
598
  lastError: unknown;
584
599
  sawRateLimit: boolean;
@@ -640,6 +655,12 @@ export type AccountQuota = {
640
655
  /** Epoch ms when we last captured this data */
641
656
  lastUpdated: number;
642
657
  };
658
+ /** Why an account is currently cooling. Drives cooldown duration and logging.
659
+ * - "weekly" : 7d unified limit rejected — cool until the weekly reset.
660
+ * - "session" : 5h unified limit rejected — cool until the session reset.
661
+ * - "transient" : short per-minute/burst 429 — cool for retry-after only.
662
+ * - "auth" : auth/refresh failures (reserved; currently rotate-only). */
663
+ export type AccountCoolingReason = "weekly" | "session" | "transient" | "auth";
643
664
  /** Runtime state for a proxy account. */
644
665
  export type RuntimeAccountState = {
645
666
  consecutiveRefreshFailures: number;
@@ -647,9 +668,16 @@ export type RuntimeAccountState = {
647
668
  lastToken?: string;
648
669
  lastRefreshToken?: string;
649
670
  /** Epoch-ms timestamp until which the account should not be used for new
650
- * requests (set after 429 retries are exhausted). Other requests arriving
651
- * during this window will skip the account rather than hammering it again. */
671
+ * requests. Set from the ACTUAL Anthropic reset window (5h/7d) on an
672
+ * exhaustion 429, or from retry-after on a transient burst. Other requests
673
+ * arriving during this window skip the account rather than hammering it. */
652
674
  coolingUntil?: number;
675
+ /** Why the account is cooling (set alongside coolingUntil). */
676
+ coolingReason?: AccountCoolingReason;
677
+ /** Latest quota snapshot parsed from Anthropic `anthropic-ratelimit-unified-*`
678
+ * headers on ANY response (success or 429). Drives proactive, reset-aware
679
+ * selection so we don't have to eat a 429 to discover an account is spent. */
680
+ quota?: AccountQuota;
653
681
  };
654
682
  /** A passthrough account used in the proxy route handler. */
655
683
  export type ProxyPassthroughAccount = {
@@ -6,15 +6,28 @@
6
6
  *
7
7
  * Architecture mirrors the Hippocampus memory subsystem: a `skills` config
8
8
  * on the NeuroLink constructor lazily initializes a SkillsManager, which
9
- * auto-registers built-in tools (search_skills / list_skills, plus gated
10
- * mutation tools) and optionally injects a compact skills index into the
11
- * system prompt of each generate()/stream() call.
9
+ * auto-registers built-in tools (list_skills, plus gated mutation tools)
10
+ * and augments each generate()/stream() call with the discovery listing
11
+ * plus per-call use_skill / read_skill_resource tools.
12
12
  *
13
13
  * Naming: every exported type carries a `Skill` prefix to satisfy the
14
14
  * `unique-type-names` ESLint rule.
15
15
  */
16
+ import type { ChatMessage } from "./conversation.js";
16
17
  /** Visibility of a skill: available everywhere, or only in specific scopes. */
17
18
  export type SkillScopeKind = "global" | "scoped";
19
+ /**
20
+ * Reference to an auxiliary file bundled with a skill (progressive
21
+ * disclosure level 3). Resources are read into context on demand via the
22
+ * read_skill_resource tool — a skill's SKILL.md should stay lean and point
23
+ * to resources for rarely-needed detail.
24
+ */
25
+ export type SkillResourceRef = {
26
+ /** Path relative to the skill's directory, e.g. "references/edge-cases.md". */
27
+ path: string;
28
+ /** Size in bytes when known (listing hint only). */
29
+ size?: number;
30
+ };
18
31
  /** Lifecycle status. Deletes are soft — deprecated skills stay in storage. */
19
32
  export type SkillLifecycleStatus = "active" | "deprecated";
20
33
  /**
@@ -47,6 +60,12 @@ export type SkillDefinition = {
47
60
  createdAt?: string;
48
61
  /** ISO timestamp of last update. */
49
62
  updatedAt?: string;
63
+ /**
64
+ * Auxiliary files bundled with the skill, readable on demand through
65
+ * read_skill_resource. Populated by stores that support resources
66
+ * (directory-layout filesystem skills, S3, Redis).
67
+ */
68
+ resources?: SkillResourceRef[];
50
69
  /** Free-form host metadata (audit fields, approval references, …). */
51
70
  metadata?: Record<string, unknown>;
52
71
  };
@@ -69,12 +88,24 @@ export type SkillStore = {
69
88
  index(): Promise<SkillIndexItem[]>;
70
89
  /** Optional: drop any internal caches (called after mutations). */
71
90
  invalidate?(): void;
91
+ /**
92
+ * Optional: fetch an auxiliary resource file bundled with a skill.
93
+ * `resourcePath` is relative to the skill (e.g. "references/forms.md").
94
+ * Null when the skill or resource is absent. Stores without resource
95
+ * support simply omit this method.
96
+ */
97
+ getResource?(id: string, resourcePath: string): Promise<string | null>;
72
98
  };
73
99
  /** In-process store, optionally seeded. Good for tests and embedded use. */
74
100
  export type SkillMemoryStorageConfig = {
75
101
  type: "memory";
76
102
  /** Initial skills to seed the store with. */
77
103
  skills?: SkillDefinition[];
104
+ /**
105
+ * Resource file contents keyed by skill id → relative path.
106
+ * E.g. `{ "my-skill": { "references/forms.md": "..." } }`.
107
+ */
108
+ resources?: Record<string, Record<string, string>>;
78
109
  };
79
110
  /**
80
111
  * Directory-backed store. Reads three layouts:
@@ -154,6 +185,15 @@ export type SkillS3IndexDocument = {
154
185
  lastUpdated: string;
155
186
  skills: SkillIndexItem[];
156
187
  };
188
+ /** Result of a conditional (ETag) object read. */
189
+ export type SkillS3ConditionalGetResult = {
190
+ /** Object body; null when the key is absent. */
191
+ body: string | null;
192
+ /** ETag of the returned body, for the next conditional read. */
193
+ etag?: string;
194
+ /** True when the object is unchanged since the supplied ETag (no body). */
195
+ notModified?: boolean;
196
+ };
157
197
  /**
158
198
  * Minimal object-storage operations the S3 skill store runs on. The
159
199
  * default implementation is created lazily from @aws-sdk/client-s3;
@@ -166,9 +206,15 @@ export type SkillS3ObjectOps = {
166
206
  deleteObject(key: string): Promise<void>;
167
207
  /** List all object keys under a prefix (paginated internally). */
168
208
  listKeys(prefix: string): Promise<string[]>;
209
+ /**
210
+ * Optional: ETag-conditional read (If-None-Match). Used for index.json
211
+ * refreshes so an unchanged index costs a 304 instead of a full download.
212
+ * Ops without it fall back to plain getObject.
213
+ */
214
+ getObjectConditional?(key: string, etag?: string): Promise<SkillS3ConditionalGetResult>;
169
215
  };
170
216
  export type SkillsStorageConfig = SkillMemoryStorageConfig | SkillFilesystemStorageConfig | SkillS3StorageConfig | SkillRedisStorageConfig | SkillCustomStorageConfig;
171
- /** Query accepted by SkillsManager.search() and the search_skills tool. */
217
+ /** Query accepted by SkillsManager.search() (programmatic + CLI search). */
172
218
  export type SkillSearchQuery = {
173
219
  /** Keyword matched (case-insensitive substring) against name, displayName, and description. */
174
220
  query?: string;
@@ -239,14 +285,38 @@ export type SkillsConfig = {
239
285
  /** Persistence backend. Default: `{ type: "memory" }`. */
240
286
  storage?: SkillsStorageConfig;
241
287
  /**
242
- * Inject a compact skills index (names + descriptions, never instructions)
243
- * into the system prompt of each generate()/stream() call. Default: true.
244
- * Set false for curator-style pure tool-driven disclosure.
288
+ * Where the skills listing (names + descriptions, never instructions)
289
+ * surfaces for model-driven discovery:
290
+ * - "tool" (default): an `<available_skills>` block embedded in the
291
+ * use_skill tool description — the Claude Code pattern. Keeps the
292
+ * host's system prompt untouched and the listing cache-stable.
293
+ * - "system-prompt": a "## Available Skills" index appended to the
294
+ * system prompt instead.
295
+ * - "none": no listing anywhere; discovery only via list_skills.
296
+ */
297
+ discovery?: SkillDiscoveryMode;
298
+ /**
299
+ * Character budget for the "tool" discovery listing. When the full
300
+ * listing exceeds it, every description is shortened uniformly (first
301
+ * sentence, then a hard cap) so the render stays a pure function of the
302
+ * index — byte-stable across calls; names are never dropped.
303
+ * Default: 15000.
245
304
  */
246
- promptIndex?: boolean;
305
+ listingBudgetChars?: number;
306
+ /**
307
+ * Pin activated skill instructions into session history so later turns
308
+ * replay them verbatim (byte-stable, provider-cacheable) instead of
309
+ * re-fetching the skill. Requires conversation memory + a sessionId on
310
+ * the call. Default: true.
311
+ */
312
+ sessionPersistence?: boolean;
247
313
  /** Maximum skills hydrated (with instructions) per search. Default: 5. */
248
314
  maxMatches?: number;
249
- /** Maximum entries rendered in the prompt index before truncation. Default: 50. */
315
+ /**
316
+ * Maximum entries rendered by the "system-prompt" discovery mode before
317
+ * truncation. Default: 50. The "tool" mode is bounded by
318
+ * listingBudgetChars instead and never drops entries.
319
+ */
250
320
  promptIndexMaxItems?: number;
251
321
  /** Index cache TTL in milliseconds. Default: 30000. 0 disables caching. */
252
322
  indexCacheTtlMs?: number;
@@ -265,6 +335,30 @@ export type SkillsConfig = {
265
335
  */
266
336
  onMutationRequest?: (action: SkillMutationAction) => Promise<SkillMutationDecision>;
267
337
  };
338
+ /** Where the skills discovery listing surfaces. See SkillsConfig.discovery. */
339
+ export type SkillDiscoveryMode = "tool" | "system-prompt" | "none";
340
+ /**
341
+ * One activated skill in a session: which skill, at which version, when.
342
+ * Sessions pin the version active at activation time — a mid-session skill
343
+ * update never mutates instructions the model has already loaded.
344
+ */
345
+ export type SkillActivationRecord = {
346
+ skillId: string;
347
+ name: string;
348
+ version: number;
349
+ /** ISO timestamp of activation. */
350
+ activatedAt: string;
351
+ };
352
+ /**
353
+ * Structural view of the per-session activation tracker consumed by the
354
+ * skill tools factory.
355
+ */
356
+ export type SkillSessionStateLike = {
357
+ isActive: (sessionId: string, skillId: string, name: string) => boolean;
358
+ getActivation: (sessionId: string, skillId: string, name?: string) => SkillActivationRecord | undefined;
359
+ recordActivation: (sessionId: string, skill: SkillDefinition) => ChatMessage;
360
+ hydrate: (sessionId: string, storedMessages: ChatMessage[]) => void;
361
+ };
268
362
  /**
269
363
  * Structural view of SkillsManager consumed by the skill tools factory —
270
364
  * keeps skillTools.ts decoupled from the concrete manager class.
@@ -272,8 +366,38 @@ export type SkillsConfig = {
272
366
  export type SkillsManagerLike = {
273
367
  search: (query: SkillSearchQuery) => Promise<SkillDefinition[]>;
274
368
  list: (scopeId?: string) => Promise<SkillIndexItem[]>;
369
+ get: (idOrName: string) => Promise<SkillDefinition | null>;
370
+ getResource: (idOrName: string, resourcePath: string) => Promise<string | null>;
371
+ sessions: SkillSessionStateLike;
275
372
  requestMutation: (action: SkillMutationAction) => Promise<SkillMutationResult>;
276
373
  };
374
+ /**
375
+ * Per-call context bound into the use_skill / read_skill_resource tools at
376
+ * injection time (prepareGenerate/prepareStream). The sessionId is captured
377
+ * by closure so activation state is tracked without relying on runtime tool
378
+ * context plumbing.
379
+ */
380
+ export type SkillCallToolsContext = {
381
+ /** Session the call belongs to; absent → activation state is per-turn only. */
382
+ sessionId?: string;
383
+ /** Scope filter applied when resolving skills for this call. */
384
+ scopeId?: string;
385
+ /**
386
+ * Pin activated instructions into session history after the turn.
387
+ * Mirrors SkillsConfig.sessionPersistence resolved for this call.
388
+ */
389
+ sessionPersistence: boolean;
390
+ /** Discovery mode resolved for this call — shapes the use_skill description. */
391
+ discovery: SkillDiscoveryMode;
392
+ /** Rendered `<available_skills>` block for "tool" discovery; null when empty. */
393
+ listing?: string | null;
394
+ /**
395
+ * Stored session history loader used to hydrate activation state before
396
+ * every dedup check (restart/multi-instance/failed-persistence safety).
397
+ * Invoked once per use_skill / read_skill_resource attempt.
398
+ */
399
+ getStoredMessages?: (sessionId: string) => Promise<ChatMessage[]>;
400
+ };
277
401
  /** Options for the createSkillTools factory. */
278
402
  export type SkillToolsOptions = {
279
403
  /** Include skill_create / skill_update / skill_delete. Default: false. */
@@ -285,12 +409,19 @@ export type SkillToolsOptions = {
285
409
  * config (same precedence convention as per-call credentials).
286
410
  */
287
411
  export type SkillsCallOptions = {
288
- /** Master toggle for this call (prompt index only tools stay registered). Default: true. */
412
+ /** Master toggle for this call (listing + per-call tools). Default: true. */
289
413
  enabled?: boolean;
290
- /** Per-call override of SkillsConfig.promptIndex. */
291
- promptIndex?: boolean;
292
- /** Scope filter for the prompt index on this call. Overrides defaultScopeId. */
414
+ /** Per-call override of SkillsConfig.discovery. */
415
+ discovery?: SkillDiscoveryMode;
416
+ /** Scope filter for the listing and skill resolution on this call. Overrides defaultScopeId. */
293
417
  scopeId?: string;
294
- /** Restrict the prompt index to skills carrying at least one of these tags. */
418
+ /** Restrict the listing to skills carrying at least one of these tags. */
295
419
  tags?: string[];
420
+ /**
421
+ * Skill names to activate at the start of this call: their full
422
+ * instructions are injected up front (and pinned to the session when
423
+ * sessionPersistence is on), without waiting for the model to invoke
424
+ * use_skill. Already-active skills are skipped.
425
+ */
426
+ preload?: string[];
296
427
  };
@@ -6,9 +6,9 @@
6
6
  *
7
7
  * Architecture mirrors the Hippocampus memory subsystem: a `skills` config
8
8
  * on the NeuroLink constructor lazily initializes a SkillsManager, which
9
- * auto-registers built-in tools (search_skills / list_skills, plus gated
10
- * mutation tools) and optionally injects a compact skills index into the
11
- * system prompt of each generate()/stream() call.
9
+ * auto-registers built-in tools (list_skills, plus gated mutation tools)
10
+ * and augments each generate()/stream() call with the discovery listing
11
+ * plus per-call use_skill / read_skill_resource tools.
12
12
  *
13
13
  * Naming: every exported type carries a `Skill` prefix to satisfy the
14
14
  * `unique-type-names` ESLint rule.
@@ -28,7 +28,7 @@ export declare function getConversationMessages(conversationMemory: Conversation
28
28
  * Store conversation turn for future context
29
29
  * Saves user messages and AI responses for conversation memory
30
30
  */
31
- export declare function storeConversationTurn(conversationMemory: ConversationMemoryManager | RedisConversationMemoryManager | null | undefined, originalOptions: TextGenerationOptions, result: TextGenerationResult, startTimeStamp?: Date | undefined, requestId?: string): Promise<void>;
31
+ export declare function storeConversationTurn(conversationMemory: ConversationMemoryManager | RedisConversationMemoryManager | null | undefined, originalOptions: TextGenerationOptions, result: TextGenerationResult, startTimeStamp?: Date | undefined, requestId?: string, skillMessages?: ChatMessage[]): Promise<void>;
32
32
  /**
33
33
  * Build context messages from pointer onwards (token-based memory)
34
34
  * Returns summary message (if exists) + all messages after the summarized pointer
@@ -227,7 +227,7 @@ export async function getConversationMessages(conversationMemory, options) {
227
227
  * Store conversation turn for future context
228
228
  * Saves user messages and AI responses for conversation memory
229
229
  */
230
- export async function storeConversationTurn(conversationMemory, originalOptions, result, startTimeStamp, requestId) {
230
+ export async function storeConversationTurn(conversationMemory, originalOptions, result, startTimeStamp, requestId, skillMessages) {
231
231
  logger.debug("[conversationMemoryUtils] storeConversationTurn called", {
232
232
  requestId,
233
233
  hasMemory: !!conversationMemory,
@@ -369,6 +369,9 @@ export async function storeConversationTurn(conversationMemory, originalOptions,
369
369
  enableSummarization: originalOptions.enableSummarization,
370
370
  requestId,
371
371
  events: toolActivityEvents,
372
+ ...(skillMessages && skillMessages.length > 0
373
+ ? { skillMessages }
374
+ : {}),
372
375
  tokenUsage: result.usage
373
376
  ? {
374
377
  inputTokens: result.usage.input,
@@ -450,6 +453,12 @@ export function buildContextFromPointer(session, requestId) {
450
453
  return session.messages;
451
454
  }
452
455
  const messagesAfterPointer = session.messages.slice(pointerIndex + 1);
456
+ // Pinned skill instructions must survive summarization: any skill message
457
+ // that fell behind the pointer is re-included verbatim after the summary,
458
+ // so an activated skill keeps its full instructions for the whole session.
459
+ const pinnedSkillMessages = session.messages
460
+ .slice(0, pointerIndex + 1)
461
+ .filter((msg) => msg.metadata?.isSkill);
453
462
  // Construct context: summary message + recent messages
454
463
  const summaryMessage = {
455
464
  id: `summary-${session.summarizedUpToMessageId}`,
@@ -468,7 +477,11 @@ export function buildContextFromPointer(session, requestId) {
468
477
  totalMessages: session.messages.length,
469
478
  summaryLength: session.summarizedMessage.length,
470
479
  });
471
- const contextMessages = [summaryMessage, ...messagesAfterPointer];
480
+ const contextMessages = [
481
+ summaryMessage,
482
+ ...pinnedSkillMessages,
483
+ ...messagesAfterPointer,
484
+ ];
472
485
  // Log context built for LLM with structural metadata
473
486
  const totalChars = contextMessages.reduce((sum, msg) => sum + msg.content.length, 0);
474
487
  logger.info("[ConversationMemory] Context built for LLM", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.85.0",
3
+ "version": "9.86.0",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applications with 21+ providers: OpenAI, Anthropic, Google AI Studio, Google Vertex, AWS Bedrock, Azure OpenAI, Mistral, LiteLLM, SageMaker, Hugging Face, Ollama, OpenAI-compatible, OpenRouter, DeepSeek, NVIDIA NIM, LM Studio, llama.cpp, plus voice (OpenAI TTS, ElevenLabs, Deepgram, Azure Speech).",
6
6
  "author": {