lemura 1.6.0 → 1.7.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 +11 -0
- package/README.md +1 -0
- package/dist/{agent-BppqfsIZ.d.ts → agent-BcAg3gCz.d.ts} +12 -1
- package/dist/{agent-B2okLlzq.d.mts → agent-Dus44yQd.d.mts} +12 -1
- package/dist/index.d.mts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +177 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +177 -16
- package/dist/index.mjs.map +1 -1
- package/dist/skills/index.d.mts +66 -6
- package/dist/skills/index.d.ts +66 -6
- package/dist/skills/index.js +91 -12
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/index.mjs +91 -12
- package/dist/skills/index.mjs.map +1 -1
- package/dist/skills-DWCC0K1B.d.mts +122 -0
- package/dist/skills-DWCC0K1B.d.ts +122 -0
- package/dist/tools/index.d.mts +2 -2
- package/dist/tools/index.d.ts +2 -2
- package/dist/types/index.d.mts +2 -2
- package/dist/types/index.d.ts +2 -2
- package/package.json +1 -1
- package/dist/skills-Y6D7zSSw.d.mts +0 -66
- package/dist/skills-Y6D7zSSw.d.ts +0 -66
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.7.0] - 2026-06-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Progressive skills — model-driven skill selection** (`strategy: 'progressive'`): A third skill strategy alongside `fixed` and `dynamic`. The model sees a lightweight catalog (`name: description`) of progressive skills in its system prompt and decides which to pull in by calling a built-in `load_skill` tool; only the chosen skill's full content is then injected. This is the "progressive disclosure" pattern — the host wires nothing, the agent selects its own skills. Fully backward-compatible (default strategy remains `fixed`).
|
|
13
|
+
- **Built-in `load_skill` tool** (`src/tools/builtin/load_skill.ts`, `createLoadSkillTool`): Auto-registered by `SessionManager` whenever a `progressive` skill is present, and auto-trusted by the tool firewall (it only toggles skill injection, no external side effects). The `name` parameter is constrained to the available progressive skills; unknown names and `maxConcurrent` violations return soft errors that keep the ReAct loop going.
|
|
14
|
+
- **`SkillSelectionConfig`** (`SessionConfig.skillSelection`): Tunes progressive selection — `persistence` (`'per_turn'` default, or `'session'`), `maxConcurrent` (cap on simultaneously loaded skills), and `catalogHeader` (override the catalog preamble). Ignored when no progressive skills are present.
|
|
15
|
+
- **`SkillInjector.buildCatalog(header?)`**: Builds the `name: description` catalog block for progressive skills. Plus `resetProgressiveSkills()`, `getProgressiveSkills()`, and `countEnabledProgressive()` helpers. `enableSkill`/`disableSkill`/`enableByTags`/`disableByTags` now operate on both `dynamic` and `progressive` skills.
|
|
16
|
+
- **Full skill-lifecycle tracing**: `skill_load` now fires for **every registered skill** (not just active ones) and carries an `enabled` flag, so inactive `dynamic`/`progressive` skills are visible from session init. New `skill_enable` trace (`{ name, source: 'load_skill' }`) records the model's selection decision when it loads a progressive skill, and new `skill_reset` trace (`{ skills, reason: 'per_turn' }`) records per-turn clearing. `session_init` reports a `progressive` skill count; `skill_inject` fires for both dynamic and progressive injections.
|
|
17
|
+
- **Tests**: `tests/unit/skills/ProgressiveSkills.test.ts` (catalog, activation, `load_skill` tool, `maxConcurrent`) and new `SessionManager` cases (auto-registration, per-turn vs session persistence).
|
|
18
|
+
|
|
8
19
|
## [1.6.0] - 2026-06-05
|
|
9
20
|
|
|
10
21
|
### Added
|
package/README.md
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
### ✨ Key Features
|
|
26
26
|
|
|
27
27
|
- **🧠 Dynamic Skill Market**: Switch skills on/off at runtime via tags, names, or tool dependencies.
|
|
28
|
+
- **📖 Progressive Skills**: Let the agent pick its own skills — it reads a catalog and loads only what's relevant via the built-in `load_skill` tool (`strategy: 'progressive'`).
|
|
28
29
|
- **🔌 Native MCP Support**: Connect to any Model Context Protocol server with custom header support (Auth).
|
|
29
30
|
- **🛡️ Tool Firewall**: Fully integrated ask/accept/deny policy layer for secure tool execution — fail-safe by design.
|
|
30
31
|
- **🎯 Goal Maintenance & Verification**: LLM-powered sub-goal decomposition, progress reconciliation, and post-run goal verification that re-enters the loop with full tool access to finish incomplete answers.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { I as IToolDefinition, a as IProviderAdapter, b as IContextStrategy, S as ShortTermMemoryRegistry, c as IScratchpadAdapter, T as Turn } from './adapters-CnsgefYR.js';
|
|
2
2
|
import { I as ILogger } from './logger-DxvKliuk.js';
|
|
3
|
-
import { I as ISkill } from './skills-
|
|
3
|
+
import { I as ISkill, S as SkillSelectionConfig } from './skills-DWCC0K1B.js';
|
|
4
4
|
import { I as IRAGAdapter } from './rag-La_Bo-J8.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -323,6 +323,17 @@ interface SessionConfig {
|
|
|
323
323
|
* @since 1.4.0
|
|
324
324
|
*/
|
|
325
325
|
activeDynamicTags?: string[];
|
|
326
|
+
/**
|
|
327
|
+
* Configuration for model-driven (`progressive`) skill selection. When any
|
|
328
|
+
* skill in `skills` has `strategy: 'progressive'`, Lemura automatically appends
|
|
329
|
+
* a skill catalog to the system prompt, registers the built-in `load_skill`
|
|
330
|
+
* tool (auto-trusted by the firewall), and resets progressive skills according
|
|
331
|
+
* to `persistence`. This object tunes that behaviour; it is ignored when no
|
|
332
|
+
* progressive skills are present.
|
|
333
|
+
*
|
|
334
|
+
* @since 1.7.0
|
|
335
|
+
*/
|
|
336
|
+
skillSelection?: SkillSelectionConfig;
|
|
326
337
|
/** RAG adapter */
|
|
327
338
|
ragAdapter?: IRAGAdapter;
|
|
328
339
|
/** Context compression strategies */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { I as IToolDefinition, a as IProviderAdapter, b as IContextStrategy, S as ShortTermMemoryRegistry, c as IScratchpadAdapter, T as Turn } from './adapters-BN6und82.mjs';
|
|
2
2
|
import { I as ILogger } from './logger-DxvKliuk.mjs';
|
|
3
|
-
import { I as ISkill } from './skills-
|
|
3
|
+
import { I as ISkill, S as SkillSelectionConfig } from './skills-DWCC0K1B.mjs';
|
|
4
4
|
import { I as IRAGAdapter } from './rag-La_Bo-J8.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -323,6 +323,17 @@ interface SessionConfig {
|
|
|
323
323
|
* @since 1.4.0
|
|
324
324
|
*/
|
|
325
325
|
activeDynamicTags?: string[];
|
|
326
|
+
/**
|
|
327
|
+
* Configuration for model-driven (`progressive`) skill selection. When any
|
|
328
|
+
* skill in `skills` has `strategy: 'progressive'`, Lemura automatically appends
|
|
329
|
+
* a skill catalog to the system prompt, registers the built-in `load_skill`
|
|
330
|
+
* tool (auto-trusted by the firewall), and resets progressive skills according
|
|
331
|
+
* to `persistence`. This object tunes that behaviour; it is ignored when no
|
|
332
|
+
* progressive skills are present.
|
|
333
|
+
*
|
|
334
|
+
* @since 1.7.0
|
|
335
|
+
*/
|
|
336
|
+
skillSelection?: SkillSelectionConfig;
|
|
326
337
|
/** RAG adapter */
|
|
327
338
|
ragAdapter?: IRAGAdapter;
|
|
328
339
|
/** Context compression strategies */
|
package/dist/index.d.mts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { a as IProviderAdapter, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse, M as ModelInfo, C as ContextWindow, T as Turn, j as ContentBlock, I as IToolDefinition } from './adapters-BN6und82.mjs';
|
|
2
2
|
export { k as CompletionChunk, l as CompletionRequest, m as CompletionResponse, b as IContextStrategy, c as IScratchpadAdapter, n as IStorageAdapter, N as NormalizedMessage, o as STMItem, p as STMRegistryConfig, S as ShortTermMemoryRegistry, q as TokenUsage, r as ToolCall, s as ToolContext, t as ToolResult } from './adapters-BN6und82.mjs';
|
|
3
|
-
import { S as SessionConfig, G as Goal, I as IToolResponseProcessor, T as ToolResponseEvaluation, a as IRouterAdapter, b as ToolCategoryInfo, R as RouterDecision, M as MCPServerConfig, c as MCPToolDefinition } from './agent-
|
|
4
|
-
export { d as GoalInjector, e as GoalVerifierResult, f as MCPJsonRpcRequest, g as MCPJsonRpcResponse, h as MCPTransportType, i as MediaConfig, j as ToolDecision, k as ToolExecutionBudget, l as ToolFirewallConfig, m as ToolFirewallRule, n as TraceEvent } from './agent-
|
|
3
|
+
import { S as SessionConfig, G as Goal, I as IToolResponseProcessor, T as ToolResponseEvaluation, a as IRouterAdapter, b as ToolCategoryInfo, R as RouterDecision, M as MCPServerConfig, c as MCPToolDefinition } from './agent-Dus44yQd.mjs';
|
|
4
|
+
export { d as GoalInjector, e as GoalVerifierResult, f as MCPJsonRpcRequest, g as MCPJsonRpcResponse, h as MCPTransportType, i as MediaConfig, j as ToolDecision, k as ToolExecutionBudget, l as ToolFirewallConfig, m as ToolFirewallRule, n as TraceEvent } from './agent-Dus44yQd.mjs';
|
|
5
5
|
export { LemuraAdapterError, LemuraContextOverflowError, LemuraError, LemuraMCPConnectionError, LemuraMCPError, LemuraMCPTimeoutError, LemuraMaxIterationsError, LemuraSkillInjectionError, LemuraToolNotFoundError, LemuraToolTimeoutError, LemuraToolValidationError } from './types/index.mjs';
|
|
6
6
|
import { I as ILogger } from './logger-DxvKliuk.mjs';
|
|
7
7
|
export { L as LogLevel, a as LogMetadata, S as Severity } from './logger-DxvKliuk.mjs';
|
|
8
8
|
export { I as IRAGAdapter, R as RAGDocument, a as RAGIngestOptions, b as RAGIngestRequest, c as RAGIngestResponse, d as RAGQueryRequest, e as RAGQueryResponse, f as RAGResult } from './rag-La_Bo-J8.mjs';
|
|
9
|
-
export { I as ISkill, S as SkillStrategy } from './skills-
|
|
9
|
+
export { I as ISkill, S as SkillSelectionConfig, a as SkillStrategy } from './skills-DWCC0K1B.mjs';
|
|
10
10
|
export { OpenAICompatibleAdapter, OpenAICompatibleAdapterConfig, RetryConfig } from './adapters/index.mjs';
|
|
11
11
|
export { ContextManager, HistoryCompressionConfig, HistoryCompressionStrategy, InMemoryScratchpadAdapter, InMemoryStorageAdapter, SandwichCompressionConfig, SandwichCompressionStrategy, ScratchpadStrategy, SummaryInjectionConfig, SummaryInjectionStrategy } from './context/index.mjs';
|
|
12
12
|
import { ToolRegistry } from './tools/index.mjs';
|
|
@@ -213,6 +213,8 @@ declare class SessionManager {
|
|
|
213
213
|
private contextManager;
|
|
214
214
|
private toolRegistry;
|
|
215
215
|
private skillInjector;
|
|
216
|
+
/** True when any registered skill uses `strategy: 'progressive'`. */
|
|
217
|
+
private hasProgressiveSkills;
|
|
216
218
|
private context;
|
|
217
219
|
private adapter;
|
|
218
220
|
private config;
|
|
@@ -428,6 +430,13 @@ declare class SessionManager {
|
|
|
428
430
|
* @throws {LemuraMaxIterationsError} When the loop exceeds `maxIterations`
|
|
429
431
|
*/
|
|
430
432
|
run(userMessage: string | ContentBlock[]): Promise<string>;
|
|
433
|
+
/**
|
|
434
|
+
* Resets progressive skills at the start of a turn when
|
|
435
|
+
* `skillSelection.persistence` is `'per_turn'` (the default), so each user
|
|
436
|
+
* message re-decides which skills to load from the catalog. No-op when
|
|
437
|
+
* persistence is `'session'` or there are no progressive skills.
|
|
438
|
+
*/
|
|
439
|
+
private _resetProgressiveSkillsForTurn;
|
|
431
440
|
/**
|
|
432
441
|
* Runs the ReAct loop and streams the final assistant response token-by-token.
|
|
433
442
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { a as IProviderAdapter, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse, M as ModelInfo, C as ContextWindow, T as Turn, j as ContentBlock, I as IToolDefinition } from './adapters-CnsgefYR.js';
|
|
2
2
|
export { k as CompletionChunk, l as CompletionRequest, m as CompletionResponse, b as IContextStrategy, c as IScratchpadAdapter, n as IStorageAdapter, N as NormalizedMessage, o as STMItem, p as STMRegistryConfig, S as ShortTermMemoryRegistry, q as TokenUsage, r as ToolCall, s as ToolContext, t as ToolResult } from './adapters-CnsgefYR.js';
|
|
3
|
-
import { S as SessionConfig, G as Goal, I as IToolResponseProcessor, T as ToolResponseEvaluation, a as IRouterAdapter, b as ToolCategoryInfo, R as RouterDecision, M as MCPServerConfig, c as MCPToolDefinition } from './agent-
|
|
4
|
-
export { d as GoalInjector, e as GoalVerifierResult, f as MCPJsonRpcRequest, g as MCPJsonRpcResponse, h as MCPTransportType, i as MediaConfig, j as ToolDecision, k as ToolExecutionBudget, l as ToolFirewallConfig, m as ToolFirewallRule, n as TraceEvent } from './agent-
|
|
3
|
+
import { S as SessionConfig, G as Goal, I as IToolResponseProcessor, T as ToolResponseEvaluation, a as IRouterAdapter, b as ToolCategoryInfo, R as RouterDecision, M as MCPServerConfig, c as MCPToolDefinition } from './agent-BcAg3gCz.js';
|
|
4
|
+
export { d as GoalInjector, e as GoalVerifierResult, f as MCPJsonRpcRequest, g as MCPJsonRpcResponse, h as MCPTransportType, i as MediaConfig, j as ToolDecision, k as ToolExecutionBudget, l as ToolFirewallConfig, m as ToolFirewallRule, n as TraceEvent } from './agent-BcAg3gCz.js';
|
|
5
5
|
export { LemuraAdapterError, LemuraContextOverflowError, LemuraError, LemuraMCPConnectionError, LemuraMCPError, LemuraMCPTimeoutError, LemuraMaxIterationsError, LemuraSkillInjectionError, LemuraToolNotFoundError, LemuraToolTimeoutError, LemuraToolValidationError } from './types/index.js';
|
|
6
6
|
import { I as ILogger } from './logger-DxvKliuk.js';
|
|
7
7
|
export { L as LogLevel, a as LogMetadata, S as Severity } from './logger-DxvKliuk.js';
|
|
8
8
|
export { I as IRAGAdapter, R as RAGDocument, a as RAGIngestOptions, b as RAGIngestRequest, c as RAGIngestResponse, d as RAGQueryRequest, e as RAGQueryResponse, f as RAGResult } from './rag-La_Bo-J8.js';
|
|
9
|
-
export { I as ISkill, S as SkillStrategy } from './skills-
|
|
9
|
+
export { I as ISkill, S as SkillSelectionConfig, a as SkillStrategy } from './skills-DWCC0K1B.js';
|
|
10
10
|
export { OpenAICompatibleAdapter, OpenAICompatibleAdapterConfig, RetryConfig } from './adapters/index.js';
|
|
11
11
|
export { ContextManager, HistoryCompressionConfig, HistoryCompressionStrategy, InMemoryScratchpadAdapter, InMemoryStorageAdapter, SandwichCompressionConfig, SandwichCompressionStrategy, ScratchpadStrategy, SummaryInjectionConfig, SummaryInjectionStrategy } from './context/index.js';
|
|
12
12
|
import { ToolRegistry } from './tools/index.js';
|
|
@@ -213,6 +213,8 @@ declare class SessionManager {
|
|
|
213
213
|
private contextManager;
|
|
214
214
|
private toolRegistry;
|
|
215
215
|
private skillInjector;
|
|
216
|
+
/** True when any registered skill uses `strategy: 'progressive'`. */
|
|
217
|
+
private hasProgressiveSkills;
|
|
216
218
|
private context;
|
|
217
219
|
private adapter;
|
|
218
220
|
private config;
|
|
@@ -428,6 +430,13 @@ declare class SessionManager {
|
|
|
428
430
|
* @throws {LemuraMaxIterationsError} When the loop exceeds `maxIterations`
|
|
429
431
|
*/
|
|
430
432
|
run(userMessage: string | ContentBlock[]): Promise<string>;
|
|
433
|
+
/**
|
|
434
|
+
* Resets progressive skills at the start of a turn when
|
|
435
|
+
* `skillSelection.persistence` is `'per_turn'` (the default), so each user
|
|
436
|
+
* message re-decides which skills to load from the catalog. No-op when
|
|
437
|
+
* persistence is `'session'` or there are no progressive skills.
|
|
438
|
+
*/
|
|
439
|
+
private _resetProgressiveSkillsForTurn;
|
|
431
440
|
/**
|
|
432
441
|
* Runs the ReAct loop and streams the final assistant response token-by-token.
|
|
433
442
|
*
|
package/dist/index.js
CHANGED
|
@@ -1183,7 +1183,7 @@ var MediaBridge = class {
|
|
|
1183
1183
|
};
|
|
1184
1184
|
|
|
1185
1185
|
// src/skills/SkillInjector.ts
|
|
1186
|
-
var SkillInjector = class {
|
|
1186
|
+
var SkillInjector = class _SkillInjector {
|
|
1187
1187
|
skills = [];
|
|
1188
1188
|
constructor(skills = []) {
|
|
1189
1189
|
this.skills = skills.map((s) => this._normalise(s));
|
|
@@ -1199,14 +1199,22 @@ var SkillInjector = class {
|
|
|
1199
1199
|
sortSkills() {
|
|
1200
1200
|
this.skills.sort((a, b) => a.priority - b.priority);
|
|
1201
1201
|
}
|
|
1202
|
+
/**
|
|
1203
|
+
* Strategies whose skills form an opt-in pool — inactive until explicitly
|
|
1204
|
+
* enabled. Both `dynamic` (host-enabled) and `progressive` (model-enabled via
|
|
1205
|
+
* the `load_skill` tool) behave this way.
|
|
1206
|
+
*/
|
|
1207
|
+
_isPoolStrategy(strategy) {
|
|
1208
|
+
return strategy === "dynamic" || strategy === "progressive";
|
|
1209
|
+
}
|
|
1202
1210
|
/**
|
|
1203
1211
|
* Normalises a skill to ensure consistent defaults.
|
|
1204
|
-
* For dynamic
|
|
1205
|
-
* For fixed skills
|
|
1212
|
+
* For pool skills (`dynamic` / `progressive`), `enabled` defaults to `false`
|
|
1213
|
+
* unless explicitly set. For `fixed` skills, `enabled` is ignored.
|
|
1206
1214
|
*/
|
|
1207
1215
|
_normalise(skill) {
|
|
1208
1216
|
const strategy = skill.strategy ?? "fixed";
|
|
1209
|
-
const enabled = strategy
|
|
1217
|
+
const enabled = this._isPoolStrategy(strategy) ? skill.enabled ?? false : true;
|
|
1210
1218
|
return { ...skill, strategy, enabled };
|
|
1211
1219
|
}
|
|
1212
1220
|
// -----------------------------------------------------------------------
|
|
@@ -1218,42 +1226,76 @@ var SkillInjector = class {
|
|
|
1218
1226
|
*/
|
|
1219
1227
|
enableSkill(name) {
|
|
1220
1228
|
const skill = this.skills.find((s) => s.name === name);
|
|
1221
|
-
if (skill && skill.strategy
|
|
1229
|
+
if (skill && this._isPoolStrategy(skill.strategy)) {
|
|
1222
1230
|
skill.enabled = true;
|
|
1223
1231
|
}
|
|
1224
1232
|
}
|
|
1225
1233
|
/**
|
|
1226
|
-
* Disable a
|
|
1234
|
+
* Disable a pool skill (`dynamic` or `progressive`) by name.
|
|
1227
1235
|
* Has no effect on fixed skills.
|
|
1228
1236
|
*/
|
|
1229
1237
|
disableSkill(name) {
|
|
1230
1238
|
const skill = this.skills.find((s) => s.name === name);
|
|
1231
|
-
if (skill && skill.strategy
|
|
1239
|
+
if (skill && this._isPoolStrategy(skill.strategy)) {
|
|
1232
1240
|
skill.enabled = false;
|
|
1233
1241
|
}
|
|
1234
1242
|
}
|
|
1235
1243
|
/**
|
|
1236
|
-
* Enable all
|
|
1244
|
+
* Enable all pool skills (`dynamic` / `progressive`) whose `tags` array
|
|
1245
|
+
* intersects with `tags`.
|
|
1237
1246
|
*/
|
|
1238
1247
|
enableByTags(tags) {
|
|
1239
1248
|
const tagSet = new Set(tags);
|
|
1240
1249
|
for (const skill of this.skills) {
|
|
1241
|
-
if (skill.strategy
|
|
1250
|
+
if (this._isPoolStrategy(skill.strategy) && skill.tags?.some((t) => tagSet.has(t))) {
|
|
1242
1251
|
skill.enabled = true;
|
|
1243
1252
|
}
|
|
1244
1253
|
}
|
|
1245
1254
|
}
|
|
1246
1255
|
/**
|
|
1247
|
-
* Disable all
|
|
1256
|
+
* Disable all pool skills (`dynamic` / `progressive`) whose `tags` array
|
|
1257
|
+
* intersects with `tags`.
|
|
1248
1258
|
*/
|
|
1249
1259
|
disableByTags(tags) {
|
|
1250
1260
|
const tagSet = new Set(tags);
|
|
1251
1261
|
for (const skill of this.skills) {
|
|
1252
|
-
if (skill.strategy
|
|
1262
|
+
if (this._isPoolStrategy(skill.strategy) && skill.tags?.some((t) => tagSet.has(t))) {
|
|
1263
|
+
skill.enabled = false;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Disable every progressive skill. Called by SessionManager between turns when
|
|
1269
|
+
* `skillSelection.persistence` is `'per_turn'` so each turn re-decides from the
|
|
1270
|
+
* catalog. Has no effect on `fixed` or `dynamic` skills.
|
|
1271
|
+
*
|
|
1272
|
+
* @since 1.7.0
|
|
1273
|
+
*/
|
|
1274
|
+
resetProgressiveSkills() {
|
|
1275
|
+
for (const skill of this.skills) {
|
|
1276
|
+
if (skill.strategy === "progressive") {
|
|
1253
1277
|
skill.enabled = false;
|
|
1254
1278
|
}
|
|
1255
1279
|
}
|
|
1256
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Returns all progressive skills, regardless of enabled state. Used to detect
|
|
1283
|
+
* whether the catalog / `load_skill` machinery should be activated.
|
|
1284
|
+
*
|
|
1285
|
+
* @since 1.7.0
|
|
1286
|
+
*/
|
|
1287
|
+
getProgressiveSkills() {
|
|
1288
|
+
return this.skills.filter((s) => s.strategy === "progressive");
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Number of currently-enabled progressive skills. Used to enforce
|
|
1292
|
+
* `skillSelection.maxConcurrent`.
|
|
1293
|
+
*
|
|
1294
|
+
* @since 1.7.0
|
|
1295
|
+
*/
|
|
1296
|
+
countEnabledProgressive() {
|
|
1297
|
+
return this.skills.filter((s) => s.strategy === "progressive" && s.enabled === true).length;
|
|
1298
|
+
}
|
|
1257
1299
|
// -----------------------------------------------------------------------
|
|
1258
1300
|
// Queries
|
|
1259
1301
|
// -----------------------------------------------------------------------
|
|
@@ -1292,8 +1334,45 @@ var SkillInjector = class {
|
|
|
1292
1334
|
return [...tools];
|
|
1293
1335
|
}
|
|
1294
1336
|
_isActive(skill) {
|
|
1295
|
-
return skill.strategy
|
|
1337
|
+
return !this._isPoolStrategy(skill.strategy) || skill.enabled === true;
|
|
1338
|
+
}
|
|
1339
|
+
// -----------------------------------------------------------------------
|
|
1340
|
+
// Catalog builder (progressive skills)
|
|
1341
|
+
// -----------------------------------------------------------------------
|
|
1342
|
+
/**
|
|
1343
|
+
* Builds a compact `name: description` catalog of all progressive skills, with
|
|
1344
|
+
* an instructional preamble telling the agent to call `load_skill` for relevant
|
|
1345
|
+
* entries. SessionManager appends this to the system prompt so the model can
|
|
1346
|
+
* decide which skills to pull in — full content is injected only on `load_skill`.
|
|
1347
|
+
*
|
|
1348
|
+
* Returns an empty string when there are no progressive skills (the catalog and
|
|
1349
|
+
* `load_skill` tool are then never activated).
|
|
1350
|
+
*
|
|
1351
|
+
* @param header - Optional override for the instructional preamble.
|
|
1352
|
+
* @returns The catalog block, or `''` when no progressive skills exist.
|
|
1353
|
+
*
|
|
1354
|
+
* @since 1.7.0
|
|
1355
|
+
*
|
|
1356
|
+
* @example
|
|
1357
|
+
* ```typescript
|
|
1358
|
+
* const catalog = injector.buildCatalog();
|
|
1359
|
+
* // You have access to specialized skills...
|
|
1360
|
+
* // Available skills:
|
|
1361
|
+
* // - summarize: Condenses text or documents concisely.
|
|
1362
|
+
* ```
|
|
1363
|
+
*/
|
|
1364
|
+
buildCatalog(header) {
|
|
1365
|
+
const progressive = this.getProgressiveSkills();
|
|
1366
|
+
if (progressive.length === 0) return "";
|
|
1367
|
+
const preamble = header ?? _SkillInjector.DEFAULT_CATALOG_HEADER;
|
|
1368
|
+
const lines = progressive.map((s) => `- ${s.name}: ${s.description}`);
|
|
1369
|
+
return `${preamble}
|
|
1370
|
+
|
|
1371
|
+
Available skills:
|
|
1372
|
+
${lines.join("\n")}`;
|
|
1296
1373
|
}
|
|
1374
|
+
/** Default instructional preamble for the progressive-skill catalog. */
|
|
1375
|
+
static DEFAULT_CATALOG_HEADER = "You have access to specialized skills. Each provides focused guidance for a particular kind of request. When a skill is relevant to the user's message, call the `load_skill` tool with its name BEFORE answering \u2014 its full instructions will then be injected for you to follow. Load only what is relevant; skip it for small talk or unrelated questions.";
|
|
1297
1376
|
// -----------------------------------------------------------------------
|
|
1298
1377
|
// Injection block builder
|
|
1299
1378
|
// -----------------------------------------------------------------------
|
|
@@ -1731,6 +1810,42 @@ function createMediaTools(prefix = defaultPrefix) {
|
|
|
1731
1810
|
return [transcribeTool, synthesizeTool, visionTool, imageGenTool];
|
|
1732
1811
|
}
|
|
1733
1812
|
|
|
1813
|
+
// src/tools/builtin/load_skill.ts
|
|
1814
|
+
var LOAD_SKILL_TOOL_NAME = "load_skill";
|
|
1815
|
+
function createLoadSkillTool(injector, config, onLoad) {
|
|
1816
|
+
const progressiveNames = injector.getProgressiveSkills().map((s) => s.name);
|
|
1817
|
+
return {
|
|
1818
|
+
name: LOAD_SKILL_TOOL_NAME,
|
|
1819
|
+
description: `Load a specialized skill by name to get its full instructions for the current turn. Call this when the user's request matches a skill listed in the "Available skills" catalog in your system prompt. Loading a skill injects its detailed guidance, which you should then follow.`,
|
|
1820
|
+
category: "utility",
|
|
1821
|
+
parameters: {
|
|
1822
|
+
type: "object",
|
|
1823
|
+
properties: {
|
|
1824
|
+
name: {
|
|
1825
|
+
type: "string",
|
|
1826
|
+
description: "The skill name, exactly as listed in the Available skills catalog.",
|
|
1827
|
+
enum: progressiveNames
|
|
1828
|
+
}
|
|
1829
|
+
},
|
|
1830
|
+
required: ["name"]
|
|
1831
|
+
},
|
|
1832
|
+
async execute(params) {
|
|
1833
|
+
const name = params?.name;
|
|
1834
|
+
if (!name || !progressiveNames.includes(name)) {
|
|
1835
|
+
return `No progressive skill named "${name}". Available: ${progressiveNames.join(", ") || "(none)"}.`;
|
|
1836
|
+
}
|
|
1837
|
+
const max = config?.maxConcurrent;
|
|
1838
|
+
const alreadyEnabled = injector.getActiveSkills().some((s) => s.name === name && s.strategy === "progressive");
|
|
1839
|
+
if (max !== void 0 && !alreadyEnabled && injector.countEnabledProgressive() >= max) {
|
|
1840
|
+
return `Cannot load "${name}": at most ${max} skill(s) may be active at once. Finish or skip a loaded skill first.`;
|
|
1841
|
+
}
|
|
1842
|
+
injector.enableSkill(name);
|
|
1843
|
+
onLoad?.(name);
|
|
1844
|
+
return `Skill "${name}" loaded. Follow its instructions for this response.`;
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1734
1849
|
// src/agent/execution/StepCounter.ts
|
|
1735
1850
|
var StepCounter = class {
|
|
1736
1851
|
constructor(maxSteps = 20) {
|
|
@@ -2693,6 +2808,8 @@ var SessionManager = class {
|
|
|
2693
2808
|
contextManager;
|
|
2694
2809
|
toolRegistry;
|
|
2695
2810
|
skillInjector;
|
|
2811
|
+
/** True when any registered skill uses `strategy: 'progressive'`. */
|
|
2812
|
+
hasProgressiveSkills = false;
|
|
2696
2813
|
context;
|
|
2697
2814
|
adapter;
|
|
2698
2815
|
config;
|
|
@@ -2787,6 +2904,17 @@ var SessionManager = class {
|
|
|
2787
2904
|
this.toolRegistry.register(tool);
|
|
2788
2905
|
}
|
|
2789
2906
|
}
|
|
2907
|
+
this.hasProgressiveSkills = this.skillInjector.getProgressiveSkills().length > 0;
|
|
2908
|
+
if (this.hasProgressiveSkills) {
|
|
2909
|
+
this.toolRegistry.register(
|
|
2910
|
+
createLoadSkillTool(
|
|
2911
|
+
this.skillInjector,
|
|
2912
|
+
config.skillSelection,
|
|
2913
|
+
// Trace the model's selection decision as a first-class skill event.
|
|
2914
|
+
(name) => this.emitTrace("skill", "skill_enable", { name, source: "load_skill" })
|
|
2915
|
+
)
|
|
2916
|
+
);
|
|
2917
|
+
}
|
|
2790
2918
|
this.context = {
|
|
2791
2919
|
systemPrompt: config.systemPrompt || "",
|
|
2792
2920
|
scratchpad: "",
|
|
@@ -2812,11 +2940,12 @@ var SessionManager = class {
|
|
|
2812
2940
|
skills: {
|
|
2813
2941
|
total: (config.skills || []).length,
|
|
2814
2942
|
active: activeSkills.length,
|
|
2815
|
-
fixed: activeSkills.filter((s) => s.strategy
|
|
2816
|
-
dynamic: activeSkills.filter((s) => s.strategy === "dynamic").length
|
|
2943
|
+
fixed: activeSkills.filter((s) => s.strategy === "fixed" || s.strategy === void 0).length,
|
|
2944
|
+
dynamic: activeSkills.filter((s) => s.strategy === "dynamic").length,
|
|
2945
|
+
progressive: this.skillInjector.getProgressiveSkills().length
|
|
2817
2946
|
}
|
|
2818
2947
|
});
|
|
2819
|
-
for (const skill of
|
|
2948
|
+
for (const skill of this.skillInjector.getAll()) {
|
|
2820
2949
|
this.emitTrace("skill", "skill_load", {
|
|
2821
2950
|
name: skill.name,
|
|
2822
2951
|
version: skill.version,
|
|
@@ -2824,7 +2953,8 @@ var SessionManager = class {
|
|
|
2824
2953
|
inject: skill.inject,
|
|
2825
2954
|
priority: skill.priority,
|
|
2826
2955
|
tags: skill.tags ?? [],
|
|
2827
|
-
requiredTools: skill.requiredTools ?? []
|
|
2956
|
+
requiredTools: skill.requiredTools ?? [],
|
|
2957
|
+
enabled: skill.enabled === true
|
|
2828
2958
|
});
|
|
2829
2959
|
}
|
|
2830
2960
|
}
|
|
@@ -3237,6 +3367,12 @@ Which pending sub-goals are now fully completed?`
|
|
|
3237
3367
|
/** Builds the system prompt, injecting skills and goal if configured. */
|
|
3238
3368
|
buildSystemPrompt(userMessage, iteration = 0) {
|
|
3239
3369
|
let prompt = this.context.systemPrompt || "";
|
|
3370
|
+
if (this.hasProgressiveSkills) {
|
|
3371
|
+
const catalog = this.skillInjector.buildCatalog(
|
|
3372
|
+
this.config.skillSelection?.catalogHeader
|
|
3373
|
+
);
|
|
3374
|
+
if (catalog) prompt += "\n\n" + catalog;
|
|
3375
|
+
}
|
|
3240
3376
|
const isStatic = this.config.staticSystemPrompt === true;
|
|
3241
3377
|
if (!isStatic && this.goalInjector && this.config.goalInjectionPosition !== "pre_turn") {
|
|
3242
3378
|
const shouldInject = this.goalInjector.shouldInjectThisTurn(
|
|
@@ -3264,6 +3400,10 @@ ${planStatus}`;
|
|
|
3264
3400
|
);
|
|
3265
3401
|
if (injectedSkills) {
|
|
3266
3402
|
prompt += "\n\n" + injectedSkills;
|
|
3403
|
+
const poolInjected = this.skillInjector.getSkillsForInjection("system_prompt").filter((s) => s.strategy === "dynamic" || s.strategy === "progressive").map((s) => s.name);
|
|
3404
|
+
if (poolInjected.length > 0) {
|
|
3405
|
+
this.emitTrace("skill", "skill_inject", { skills: poolInjected, position: "system_prompt" });
|
|
3406
|
+
}
|
|
3267
3407
|
}
|
|
3268
3408
|
return prompt.trim();
|
|
3269
3409
|
}
|
|
@@ -3450,6 +3590,9 @@ ${blocks.join("\n\n")}
|
|
|
3450
3590
|
* Returns true to proceed, false to block.
|
|
3451
3591
|
*/
|
|
3452
3592
|
async passesFirewall(toolName, argsJson, toolCallId, toolResults) {
|
|
3593
|
+
if (toolName === LOAD_SKILL_TOOL_NAME && this.hasProgressiveSkills) {
|
|
3594
|
+
return true;
|
|
3595
|
+
}
|
|
3453
3596
|
const firewall = evaluateToolFirewall(
|
|
3454
3597
|
this.config.toolFirewall,
|
|
3455
3598
|
toolName,
|
|
@@ -3649,8 +3792,25 @@ ${blocks.join("\n\n")}
|
|
|
3649
3792
|
async run(userMessage) {
|
|
3650
3793
|
if (this.mcpReady) await this.mcpReady;
|
|
3651
3794
|
await this.ensureScratchpadLoaded();
|
|
3795
|
+
this._resetProgressiveSkillsForTurn();
|
|
3652
3796
|
return this._executeLoop(userMessage, { label: "run" });
|
|
3653
3797
|
}
|
|
3798
|
+
/**
|
|
3799
|
+
* Resets progressive skills at the start of a turn when
|
|
3800
|
+
* `skillSelection.persistence` is `'per_turn'` (the default), so each user
|
|
3801
|
+
* message re-decides which skills to load from the catalog. No-op when
|
|
3802
|
+
* persistence is `'session'` or there are no progressive skills.
|
|
3803
|
+
*/
|
|
3804
|
+
_resetProgressiveSkillsForTurn() {
|
|
3805
|
+
if (!this.hasProgressiveSkills) return;
|
|
3806
|
+
const persistence = this.config.skillSelection?.persistence ?? "per_turn";
|
|
3807
|
+
if (persistence !== "per_turn") return;
|
|
3808
|
+
const cleared = this.skillInjector.getProgressiveSkills().filter((s) => s.enabled === true).map((s) => s.name);
|
|
3809
|
+
this.skillInjector.resetProgressiveSkills();
|
|
3810
|
+
if (cleared.length > 0) {
|
|
3811
|
+
this.emitTrace("skill", "skill_reset", { skills: cleared, reason: "per_turn" });
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3654
3814
|
/**
|
|
3655
3815
|
* Runs the ReAct loop and streams the final assistant response token-by-token.
|
|
3656
3816
|
*
|
|
@@ -3670,6 +3830,7 @@ ${blocks.join("\n\n")}
|
|
|
3670
3830
|
async *stream(userMessage) {
|
|
3671
3831
|
if (this.mcpReady) await this.mcpReady;
|
|
3672
3832
|
await this.ensureScratchpadLoaded();
|
|
3833
|
+
this._resetProgressiveSkillsForTurn();
|
|
3673
3834
|
const userMessageStr = Array.isArray(userMessage) ? "[Multimodal Content]" : userMessage;
|
|
3674
3835
|
this.logger.info(`Starting streaming session run`, { model: this.config.model, message: userMessageStr });
|
|
3675
3836
|
const routeDecision = await this._runRoutingStep(userMessageStr);
|