lemura 1.2.0 → 1.4.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 (39) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/README.md +12 -11
  3. package/dist/adapters/index.d.mts +1 -1
  4. package/dist/adapters/index.d.ts +1 -1
  5. package/dist/adapters/index.js +10 -2
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/adapters/index.mjs +10 -2
  8. package/dist/adapters/index.mjs.map +1 -1
  9. package/dist/{storage-CG3nTa6o.d.mts → adapters-BhTAnrOM.d.mts} +79 -46
  10. package/dist/{storage-DBt_q0wO.d.ts → adapters-CVcfWf85.d.ts} +79 -46
  11. package/dist/{agent-DxRd93wl.d.ts → agent-38El9_yp.d.ts} +35 -4
  12. package/dist/{agent-D6uhF-CZ.d.mts → agent-CJvmcAbT.d.mts} +35 -4
  13. package/dist/context/index.d.mts +71 -7
  14. package/dist/context/index.d.ts +71 -7
  15. package/dist/context/index.js +75 -4
  16. package/dist/context/index.js.map +1 -1
  17. package/dist/context/index.mjs +74 -5
  18. package/dist/context/index.mjs.map +1 -1
  19. package/dist/index.d.mts +339 -63
  20. package/dist/index.d.ts +339 -63
  21. package/dist/index.js +1018 -132
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +1017 -133
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/skills/index.d.mts +92 -3
  26. package/dist/skills/index.d.ts +92 -3
  27. package/dist/skills/index.js +138 -8
  28. package/dist/skills/index.js.map +1 -1
  29. package/dist/skills/index.mjs +138 -8
  30. package/dist/skills/index.mjs.map +1 -1
  31. package/dist/skills-Y6D7zSSw.d.mts +66 -0
  32. package/dist/skills-Y6D7zSSw.d.ts +66 -0
  33. package/dist/tools/index.d.mts +3 -3
  34. package/dist/tools/index.d.ts +3 -3
  35. package/dist/types/index.d.mts +3 -3
  36. package/dist/types/index.d.ts +3 -3
  37. package/package.json +6 -1
  38. package/dist/skills-wc8S-OvC.d.mts +0 -14
  39. package/dist/skills-wc8S-OvC.d.ts +0 -14
package/dist/index.mjs CHANGED
@@ -424,9 +424,17 @@ var OpenAICompatibleAdapter = class {
424
424
  })
425
425
  });
426
426
  const data = await response.json();
427
+ if (data?.error) {
428
+ throw new LemuraAdapterError(data.error.message || "Image generation failed", "IMAGE_GEN_ERROR");
429
+ }
430
+ const first = data?.data?.[0] || {};
431
+ const imageUrl = first.url || (first.b64_json ? `data:image/png;base64,${first.b64_json}` : null) || (first.image_url?.url || null);
432
+ if (!imageUrl) {
433
+ throw new LemuraAdapterError("Image generation returned no image URL or base64 payload", "IMAGE_GEN_EMPTY");
434
+ }
427
435
  return {
428
- imageUrl: data.data[0].url,
429
- revisedPrompt: data.data[0].revised_prompt
436
+ imageUrl,
437
+ revisedPrompt: first.revised_prompt
430
438
  };
431
439
  }
432
440
  };
@@ -454,6 +462,8 @@ var ContextManager = class {
454
462
  */
455
463
  async prepare(context, safetyMargin = 0.95) {
456
464
  let currentCtx = { ...context, turns: [...context.turns] };
465
+ const systemTokens = currentCtx.systemPrompt ? Math.ceil(currentCtx.systemPrompt.length / 4) : 0;
466
+ currentCtx.tokenCount = currentCtx.turns.reduce((sum, t) => sum + t.tokenCount, 0) + systemTokens;
457
467
  currentCtx.maxTokens * safetyMargin;
458
468
  for (const strategy of this.strategies) {
459
469
  if (strategy.shouldApply(currentCtx)) {
@@ -474,11 +484,13 @@ var SandwichCompressionStrategy = class {
474
484
  constructor(adapter, config) {
475
485
  this.adapter = adapter;
476
486
  this.config = config;
487
+ this.priority = config.priority ?? 20;
477
488
  }
478
489
  name = "sandwich_compression";
479
- priority = 20;
490
+ priority;
480
491
  shouldApply(ctx) {
481
- return ctx.tokenCount >= ctx.maxTokens * this.config.triggerThreshold && ctx.turns.length > this.config.preserveFirst + this.config.preserveLast;
492
+ const threshold = this.config.triggerThreshold ?? 0.8;
493
+ return ctx.tokenCount >= ctx.maxTokens * threshold && ctx.turns.length > this.config.preserveFirst + this.config.preserveLast;
482
494
  }
483
495
  async apply(ctx) {
484
496
  const { preserveFirst, preserveLast } = this.config;
@@ -492,7 +504,8 @@ var SandwichCompressionStrategy = class {
492
504
  role: "user",
493
505
  content: `Summarize the following conversation history briefly:
494
506
  ${middleText}`
495
- }]
507
+ }],
508
+ ...this.config.summaryMaxTokens ? { maxTokens: this.config.summaryMaxTokens } : {}
496
509
  });
497
510
  const summaryStr = summaryResponse.content;
498
511
  const newCompressionSummary = ctx.compressionSummary ? `${ctx.compressionSummary}
@@ -560,9 +573,10 @@ var HistoryCompressionStrategy = class {
560
573
  constructor(adapter, config) {
561
574
  this.adapter = adapter;
562
575
  this.config = config;
576
+ this.priority = config.priority ?? 30;
563
577
  }
564
578
  name = "history_compression";
565
- priority = 30;
579
+ priority;
566
580
  shouldApply(ctx) {
567
581
  const triggerTokens = ctx.maxTokens * this.config.triggerAtPercent;
568
582
  const uncompressedTurns = ctx.turns.filter((t) => t.role !== "system" && !t.compressed);
@@ -594,6 +608,52 @@ ${summaryStr}` : summaryStr;
594
608
  };
595
609
  }
596
610
  };
611
+
612
+ // src/context/SummaryInjectionStrategy.ts
613
+ var SummaryInjectionStrategy = class {
614
+ name = "summary_injection";
615
+ priority;
616
+ label;
617
+ constructor(config = {}) {
618
+ this.priority = config.priority ?? 1;
619
+ this.label = config.label ?? "Earlier conversation summary";
620
+ }
621
+ shouldApply(ctx) {
622
+ return !!ctx.compressionSummary && ctx.compressionSummary.trim().length > 0;
623
+ }
624
+ async apply(ctx) {
625
+ const summaryContent = `[${this.label}]
626
+ ${ctx.compressionSummary}`;
627
+ const summaryTokenCount = Math.ceil(summaryContent.length / 4);
628
+ const existingIndex = ctx.turns.findIndex((t) => t.compressed && t.role === "system" && t.turnIndex === -1);
629
+ let newTurns;
630
+ if (existingIndex !== -1) {
631
+ newTurns = ctx.turns.map((t, i) => {
632
+ if (i !== existingIndex) return t;
633
+ return {
634
+ ...t,
635
+ content: summaryContent,
636
+ tokenCount: summaryTokenCount
637
+ };
638
+ });
639
+ } else {
640
+ const summaryTurn = {
641
+ role: "system",
642
+ content: summaryContent,
643
+ tokenCount: summaryTokenCount,
644
+ turnIndex: -1,
645
+ compressed: true
646
+ };
647
+ newTurns = [summaryTurn, ...ctx.turns];
648
+ }
649
+ const newTokenCount = newTurns.reduce((sum, t) => sum + t.tokenCount, 0);
650
+ return {
651
+ ...ctx,
652
+ turns: newTurns,
653
+ tokenCount: newTokenCount
654
+ };
655
+ }
656
+ };
597
657
  var ShortTermMemoryRegistry = class {
598
658
  storage;
599
659
  maxTextTokens;
@@ -716,6 +776,23 @@ var InMemoryStorageAdapter = class {
716
776
  }
717
777
  };
718
778
 
779
+ // src/context/InMemoryScratchpadAdapter.ts
780
+ var InMemoryScratchpadAdapter = class {
781
+ store = /* @__PURE__ */ new Map();
782
+ async read(sessionId) {
783
+ return this.store.get(sessionId);
784
+ }
785
+ async write(sessionId, content) {
786
+ this.store.set(sessionId, content);
787
+ }
788
+ async clear(sessionId) {
789
+ this.store.delete(sessionId);
790
+ }
791
+ async healthCheck() {
792
+ return true;
793
+ }
794
+ };
795
+
719
796
  // src/tools/SchemaValidator.ts
720
797
  function typeOf(value) {
721
798
  if (value === null) return "null";
@@ -1053,35 +1130,165 @@ var MediaBridge = class {
1053
1130
  var SkillInjector = class {
1054
1131
  skills = [];
1055
1132
  constructor(skills = []) {
1056
- this.skills = [...skills];
1133
+ this.skills = skills.map((s) => this._normalise(s));
1057
1134
  this.sortSkills();
1058
1135
  }
1136
+ // -----------------------------------------------------------------------
1137
+ // Registration
1138
+ // -----------------------------------------------------------------------
1059
1139
  register(skill) {
1060
- this.skills.push(skill);
1140
+ this.skills.push(this._normalise(skill));
1061
1141
  this.sortSkills();
1062
1142
  }
1063
1143
  sortSkills() {
1064
1144
  this.skills.sort((a, b) => a.priority - b.priority);
1065
1145
  }
1146
+ /**
1147
+ * Normalises a skill to ensure consistent defaults.
1148
+ * For dynamic skills, `enabled` defaults to `false` unless explicitly set.
1149
+ * For fixed skills (or those without a strategy), `enabled` is ignored.
1150
+ */
1151
+ _normalise(skill) {
1152
+ const strategy = skill.strategy ?? "fixed";
1153
+ const enabled = strategy === "dynamic" ? skill.enabled ?? false : true;
1154
+ return { ...skill, strategy, enabled };
1155
+ }
1156
+ // -----------------------------------------------------------------------
1157
+ // Dynamic skill activation
1158
+ // -----------------------------------------------------------------------
1159
+ /**
1160
+ * Enable a dynamic skill by name.
1161
+ * Has no effect on fixed skills (they are always active).
1162
+ */
1163
+ enableSkill(name) {
1164
+ const skill = this.skills.find((s) => s.name === name);
1165
+ if (skill && skill.strategy === "dynamic") {
1166
+ skill.enabled = true;
1167
+ }
1168
+ }
1169
+ /**
1170
+ * Disable a dynamic skill by name.
1171
+ * Has no effect on fixed skills.
1172
+ */
1173
+ disableSkill(name) {
1174
+ const skill = this.skills.find((s) => s.name === name);
1175
+ if (skill && skill.strategy === "dynamic") {
1176
+ skill.enabled = false;
1177
+ }
1178
+ }
1179
+ /**
1180
+ * Enable all dynamic skills whose `tags` array intersects with `tags`.
1181
+ */
1182
+ enableByTags(tags) {
1183
+ const tagSet = new Set(tags);
1184
+ for (const skill of this.skills) {
1185
+ if (skill.strategy === "dynamic" && skill.tags?.some((t) => tagSet.has(t))) {
1186
+ skill.enabled = true;
1187
+ }
1188
+ }
1189
+ }
1190
+ /**
1191
+ * Disable all dynamic skills whose `tags` array intersects with `tags`.
1192
+ */
1193
+ disableByTags(tags) {
1194
+ const tagSet = new Set(tags);
1195
+ for (const skill of this.skills) {
1196
+ if (skill.strategy === "dynamic" && skill.tags?.some((t) => tagSet.has(t))) {
1197
+ skill.enabled = false;
1198
+ }
1199
+ }
1200
+ }
1201
+ // -----------------------------------------------------------------------
1202
+ // Queries
1203
+ // -----------------------------------------------------------------------
1204
+ /**
1205
+ * Returns all registered skills (fixed and dynamic, enabled or not).
1206
+ */
1207
+ getAll() {
1208
+ return [...this.skills];
1209
+ }
1210
+ /**
1211
+ * Returns currently active skills — fixed skills always, dynamic skills
1212
+ * only when `enabled === true`.
1213
+ */
1214
+ getActiveSkills() {
1215
+ return this.skills.filter((s) => this._isActive(s));
1216
+ }
1217
+ /**
1218
+ * Returns skills at a given injection position, active only.
1219
+ */
1066
1220
  getSkillsForInjection(position) {
1067
- return this.skills.filter((s) => s.inject === position);
1221
+ return this.skills.filter((s) => s.inject === position && this._isActive(s));
1222
+ }
1223
+ /**
1224
+ * Returns the union of `requiredTools` from all currently active skills.
1225
+ * Use this to determine which tools the active skill set depends on.
1226
+ *
1227
+ * @since 1.4.0
1228
+ */
1229
+ getRequiredTools() {
1230
+ const tools = /* @__PURE__ */ new Set();
1231
+ for (const skill of this.getActiveSkills()) {
1232
+ for (const t of skill.requiredTools ?? []) {
1233
+ tools.add(t);
1234
+ }
1235
+ }
1236
+ return [...tools];
1237
+ }
1238
+ _isActive(skill) {
1239
+ return skill.strategy !== "dynamic" || skill.enabled === true;
1068
1240
  }
1241
+ // -----------------------------------------------------------------------
1242
+ // Injection block builder
1243
+ // -----------------------------------------------------------------------
1069
1244
  /**
1070
- * Generates a combined prompt block for all skills targeting a specific injection position.
1245
+ * Builds the combined injection block for all active skills at the given position.
1246
+ *
1247
+ * @param position - Which injection point to target.
1248
+ * @param tokenBudget - Optional maximum token budget. Skills are added in priority
1249
+ * order until the budget would be exceeded. When `undefined`, all active skills
1250
+ * at the position are included.
1071
1251
  */
1072
- buildInjectionBlock(position) {
1252
+ buildInjectionBlock(position, tokenBudget) {
1073
1253
  const relevantSkills = this.getSkillsForInjection(position);
1074
1254
  if (relevantSkills.length === 0) return "";
1075
1255
  let block = "";
1256
+ let usedTokens = 0;
1076
1257
  for (const skill of relevantSkills) {
1077
- const content = skill.standard || skill.micro || skill.nano || skill.description;
1078
- block += `
1079
- [Skill: ${skill.name} (Tier: ${skill.tier})]
1258
+ const content = this._pickContent(skill, tokenBudget !== void 0);
1259
+ if (!content) continue;
1260
+ const tierLabel = skill.tier ?? "standard";
1261
+ const skillEntry = `
1262
+ [Skill: ${skill.name} (Tier: ${tierLabel})]
1080
1263
  ${content}
1081
1264
  `;
1265
+ const skillTokens = Math.ceil(skillEntry.length / 4);
1266
+ if (tokenBudget !== void 0 && usedTokens + skillTokens > tokenBudget) {
1267
+ continue;
1268
+ }
1269
+ block += skillEntry;
1270
+ usedTokens += skillTokens;
1082
1271
  }
1083
1272
  return block.trim();
1084
1273
  }
1274
+ // -----------------------------------------------------------------------
1275
+ // Content picking
1276
+ // -----------------------------------------------------------------------
1277
+ /**
1278
+ * Picks the content variant to use for a skill.
1279
+ *
1280
+ * Resolution order when budget-aware (compact first):
1281
+ * nano → micro → standard → content → description
1282
+ *
1283
+ * Resolution order when not budget-aware (richest first):
1284
+ * standard → content → micro → nano → description
1285
+ */
1286
+ _pickContent(skill, budgetAware) {
1287
+ if (budgetAware) {
1288
+ return skill.nano || skill.micro || skill.standard || skill.content || skill.description || "";
1289
+ }
1290
+ return skill.standard || skill.content || skill.micro || skill.nano || skill.description || "";
1291
+ }
1085
1292
  };
1086
1293
 
1087
1294
  // src/rag/InMemoryRAGAdapter.ts
@@ -1293,6 +1500,10 @@ var readScratchpadTool = {
1293
1500
  description: "Reads the content from the current agent scratchpad.",
1294
1501
  parameters: { type: "object", properties: {} },
1295
1502
  async execute(_params, context) {
1503
+ if (context.scratchpadAdapter) {
1504
+ const stored = await context.scratchpadAdapter.read(context.sessionId);
1505
+ return stored ?? "";
1506
+ }
1296
1507
  return context.scratchpad ?? "";
1297
1508
  }
1298
1509
  };
@@ -1308,12 +1519,20 @@ var writeScratchpadTool = {
1308
1519
  required: ["content"]
1309
1520
  },
1310
1521
  async execute(params, context) {
1311
- let newScratchpad = context.scratchpad ?? "";
1522
+ let current = context.scratchpad ?? "";
1523
+ if (context.scratchpadAdapter) {
1524
+ const stored = await context.scratchpadAdapter.read(context.sessionId);
1525
+ if (stored !== void 0) current = stored;
1526
+ }
1527
+ let newScratchpad = current;
1312
1528
  if (params.append) {
1313
1529
  newScratchpad += (newScratchpad ? "\n" : "") + params.content;
1314
1530
  } else {
1315
1531
  newScratchpad = params.content;
1316
1532
  }
1533
+ if (context.scratchpadAdapter) {
1534
+ await context.scratchpadAdapter.write(context.sessionId, newScratchpad);
1535
+ }
1317
1536
  return { status: "success", newScratchpad, note: "Scratchpad updated" };
1318
1537
  }
1319
1538
  };
@@ -1321,7 +1540,10 @@ var removeScratchpadTool = {
1321
1540
  name: "remove_scratchpad",
1322
1541
  description: "Clears or removes content from the scratchpad.",
1323
1542
  parameters: { type: "object", properties: {} },
1324
- async execute(_params, _context) {
1543
+ async execute(_params, context) {
1544
+ if (context.scratchpadAdapter) {
1545
+ await context.scratchpadAdapter.clear(context.sessionId);
1546
+ }
1325
1547
  return { status: "success", newScratchpad: "", note: "Scratchpad cleared" };
1326
1548
  }
1327
1549
  };
@@ -1502,30 +1724,63 @@ var FinalResponseFormatter = class {
1502
1724
 
1503
1725
  // src/agent/execution/ToolResponseProcessor.ts
1504
1726
  var ToolResponseProcessor = class {
1727
+ smallMax;
1728
+ mediumMax;
1729
+ largeMax;
1730
+ budgetPercent;
1731
+ constructor(config = {}) {
1732
+ this.smallMax = config.smallMaxTokens ?? 200;
1733
+ this.mediumMax = config.mediumMaxTokens ?? 800;
1734
+ this.largeMax = config.largeMaxTokens ?? 2e3;
1735
+ this.budgetPercent = config.budgetPercent;
1736
+ }
1505
1737
  evaluate(response, tool, context) {
1506
- const length = response.length;
1507
- const estimatedTokens = length / 4;
1738
+ const estimatedTokens = response.length / 4;
1508
1739
  let sizeClass = "small";
1509
- if (estimatedTokens > 2e3) sizeClass = "oversized";
1510
- else if (estimatedTokens > 800) sizeClass = "large";
1511
- else if (estimatedTokens > 200) sizeClass = "medium";
1740
+ if (estimatedTokens > this.largeMax) sizeClass = "oversized";
1741
+ else if (estimatedTokens > this.mediumMax) sizeClass = "large";
1742
+ else if (estimatedTokens > this.smallMax) sizeClass = "medium";
1743
+ const lc = response.toLowerCase();
1744
+ const errorDetected = lc.includes("error:") || lc.includes("exception:") || lc.includes('"error"') || lc.includes('"status":"error"') || lc.includes('"status": "error"') || lc.includes("failed:") || lc.includes("connection refused") || lc.includes("timed out");
1745
+ const suggestedAction = errorDetected ? "retry" : sizeClass === "oversized" || sizeClass === "large" ? "continue" : "continue";
1512
1746
  return {
1513
1747
  relevanceScore: 1,
1514
1748
  sizeClass,
1515
1749
  shouldCompress: sizeClass === "large" || sizeClass === "oversized",
1516
- suggestedMaxTokens: 500,
1517
- answered: true,
1518
- answeredPartially: false,
1519
- errorDetected: response.toLowerCase().includes("error"),
1520
- suggestedAction: "continue"
1750
+ suggestedMaxTokens: this.mediumMax,
1751
+ answered: !errorDetected,
1752
+ answeredPartially: errorDetected,
1753
+ errorDetected,
1754
+ suggestedAction
1521
1755
  };
1522
1756
  }
1523
1757
  compress(response, evaluation) {
1524
1758
  if (!evaluation.shouldCompress || evaluation.errorDetected) {
1525
1759
  return response;
1526
1760
  }
1527
- if (response.length > 1e3) {
1528
- return response.substring(0, 1e3) + "\n...[COMPRESSED TO SAVE TOKENS]...";
1761
+ if (evaluation.sizeClass === "oversized") {
1762
+ const headChars = this.largeMax * 4;
1763
+ const tailChars = this.mediumMax * 2;
1764
+ if (response.length > headChars + tailChars) {
1765
+ const skipped = response.length - headChars - tailChars;
1766
+ return response.slice(0, headChars) + `
1767
+
1768
+ ...[${skipped} characters omitted \u2014 response too large]...
1769
+
1770
+ ` + response.slice(-tailChars);
1771
+ }
1772
+ }
1773
+ if (evaluation.sizeClass === "large") {
1774
+ const lines = response.split("\n");
1775
+ const keepLines = Math.ceil(this.mediumMax / 10);
1776
+ if (lines.length > keepLines * 2) {
1777
+ const skipped = lines.length - keepLines * 2;
1778
+ return [
1779
+ ...lines.slice(0, keepLines),
1780
+ `... [${skipped} lines omitted] ...`,
1781
+ ...lines.slice(-keepLines)
1782
+ ].join("\n");
1783
+ }
1529
1784
  }
1530
1785
  return response;
1531
1786
  }
@@ -1533,25 +1788,262 @@ var ToolResponseProcessor = class {
1533
1788
 
1534
1789
  // src/agent/execution/GoalInjector.ts
1535
1790
  var GoalInjector = class {
1791
+ goal;
1792
+ turnsSinceInjection = 0;
1536
1793
  constructor(goal) {
1537
- this.goal = goal;
1794
+ this.goal = {
1795
+ ...goal,
1796
+ completedSubGoals: goal.completedSubGoals ?? []
1797
+ };
1538
1798
  }
1539
- injectInto(prompt) {
1540
- const formatting = `[CURRENT GOAL]
1541
- ${this.goal.statement}
1542
-
1799
+ /**
1800
+ * Returns the formatted `[CURRENT GOAL]` block string — without caring about
1801
+ * where it will be placed. Callers decide whether to append to a system prompt
1802
+ * or push as a separate message.
1803
+ */
1804
+ getFormattedBlock() {
1805
+ const { statement, successCriteria, decomposition, completedSubGoals = [] } = this.goal;
1806
+ const pending = decomposition.filter((sg) => !completedSubGoals.includes(sg));
1807
+ const completed = decomposition.filter((sg) => completedSubGoals.includes(sg));
1808
+ let block = `[CURRENT GOAL]
1809
+ ${statement}
1810
+ `;
1811
+ if (successCriteria.length > 0) {
1812
+ block += `
1543
1813
  Success criteria:
1544
- ${this.goal.successCriteria.map((c) => `- ${c}`).join("\n")}
1545
- [/CURRENT GOAL]`;
1546
- if (this.goal.injectionPosition === "system_prompt") {
1547
- return `${prompt}
1814
+ ${successCriteria.map((c) => `- ${c}`).join("\n")}`;
1815
+ }
1816
+ if (pending.length > 0) {
1817
+ block += `
1548
1818
 
1549
- ${formatting}`;
1819
+ Sub-goals remaining:
1820
+ ${pending.map((sg) => `- ${sg} \u2190 pending`).join("\n")}`;
1550
1821
  }
1551
- return prompt;
1822
+ if (completed.length > 0) {
1823
+ block += `
1824
+
1825
+ Sub-goals completed:
1826
+ ${completed.map((sg) => `- \u2705 ${sg}`).join("\n")}`;
1827
+ }
1828
+ block += "\n[/CURRENT GOAL]";
1829
+ return block;
1552
1830
  }
1831
+ /**
1832
+ * Appends the goal block to the given prompt string (for `system_prompt` position).
1833
+ * For `pre_turn` position, use `getFormattedBlock()` directly.
1834
+ *
1835
+ * @param prompt - The existing system prompt to append to.
1836
+ */
1837
+ injectInto(prompt) {
1838
+ const block = this.getFormattedBlock();
1839
+ return prompt ? `${prompt}
1840
+
1841
+ ${block}` : block;
1842
+ }
1843
+ /**
1844
+ * Returns true when the goal should be re-injected this turn,
1845
+ * based on `injectionFrequency`.
1846
+ *
1847
+ * @param turnIndex - The current turn index in the ReAct loop (0-based)
1848
+ * @param compressionOccurred - Whether context was compressed this iteration
1849
+ * @param injectionN - The N for 'every_N_turns' frequency (default: 3)
1850
+ */
1851
+ shouldInjectThisTurn(turnIndex, compressionOccurred = false, injectionN = 3) {
1852
+ const { injectionFrequency } = this.goal;
1853
+ if (injectionFrequency === "always") return true;
1854
+ if (injectionFrequency === "every_N_turns") {
1855
+ return turnIndex % injectionN === 0;
1856
+ }
1857
+ if (injectionFrequency === "on_compression") {
1858
+ return compressionOccurred;
1859
+ }
1860
+ return true;
1861
+ }
1862
+ /**
1863
+ * Updates the goal with new sub-goal decomposition and success criteria,
1864
+ * typically populated by the mini-planning LLM call.
1865
+ */
1866
+ updateDecomposition(decomposition, successCriteria) {
1867
+ this.goal = {
1868
+ ...this.goal,
1869
+ decomposition,
1870
+ ...successCriteria ? { successCriteria } : {}
1871
+ };
1872
+ }
1873
+ /**
1874
+ * Marks a sub-goal as completed so it moves to the "completed" section
1875
+ * in subsequent injections.
1876
+ */
1877
+ markSubGoalDone(subGoal) {
1878
+ const completed = this.goal.completedSubGoals ?? [];
1879
+ if (!completed.includes(subGoal)) {
1880
+ this.goal = {
1881
+ ...this.goal,
1882
+ completedSubGoals: [...completed, subGoal]
1883
+ };
1884
+ }
1885
+ }
1886
+ /** Returns a snapshot of the current goal state (safe to store in context.metadata). */
1553
1887
  getGoal() {
1554
- return { ...this.goal };
1888
+ return { ...this.goal, completedSubGoals: [...this.goal.completedSubGoals ?? []] };
1889
+ }
1890
+ /** Increments the internal turn counter (used for `every_N_turns` frequency). */
1891
+ incrementTurn() {
1892
+ this.turnsSinceInjection++;
1893
+ }
1894
+ };
1895
+
1896
+ // src/agent/execution/ContinuationPlanner.ts
1897
+ var ContinuationPlanner = class {
1898
+ plan;
1899
+ outputs = /* @__PURE__ */ new Map();
1900
+ constructor(plan) {
1901
+ this.plan = { ...plan, steps: plan.steps.map((s) => ({ ...s })) };
1902
+ }
1903
+ // -------------------------------------------------------------------------
1904
+ // State queries
1905
+ // -------------------------------------------------------------------------
1906
+ /** Returns the current plan (deep copy) */
1907
+ getPlan() {
1908
+ return { ...this.plan, steps: this.plan.steps.map((s) => ({ ...s })) };
1909
+ }
1910
+ /** Returns a human-readable status string with icons (injected before each iteration) */
1911
+ getPlanStatusString() {
1912
+ let result = `[CONTINUATION PLAN \u2014 Step ${this.plan.currentStepIndex + 1}/${this.plan.steps.length}]
1913
+ `;
1914
+ for (const step of this.plan.steps) {
1915
+ const icon = this._icon(step.status);
1916
+ const statusText = step.status === "pending" && step.dependsOn.length > 0 ? `Waiting on Step ${step.dependsOn.join(", ")}` : step.status.charAt(0).toUpperCase() + step.status.slice(1);
1917
+ result += `${icon} Step ${step.stepId} (${step.toolName}): ${statusText}
1918
+ `;
1919
+ }
1920
+ return result;
1921
+ }
1922
+ _icon(status) {
1923
+ switch (status) {
1924
+ case "done":
1925
+ return "\u2705";
1926
+ case "running":
1927
+ return "\u25B6";
1928
+ case "failed":
1929
+ return "\u274C";
1930
+ case "skipped":
1931
+ return "\u23ED";
1932
+ default:
1933
+ return "\u23F3";
1934
+ }
1935
+ }
1936
+ /** Returns all steps whose dependencies are satisfied and that are still pending */
1937
+ getReadySteps() {
1938
+ return this.plan.steps.filter((step) => {
1939
+ if (step.status !== "pending") return false;
1940
+ const depsOk = step.dependsOn.every((depId) => {
1941
+ const dep = this.plan.steps.find((s) => s.stepId === depId);
1942
+ return dep?.status === "done";
1943
+ });
1944
+ if (!depsOk) return false;
1945
+ if (step.condition) {
1946
+ const condDepOutput = this.outputs.get(step.condition.step) ?? "";
1947
+ if (!condDepOutput.includes(step.condition.outputContains)) {
1948
+ return false;
1949
+ }
1950
+ }
1951
+ return true;
1952
+ });
1953
+ }
1954
+ /** Returns true when all steps have reached a terminal state */
1955
+ isComplete() {
1956
+ return this.plan.steps.every(
1957
+ (s) => s.status === "done" || s.status === "failed" || s.status === "skipped"
1958
+ );
1959
+ }
1960
+ // -------------------------------------------------------------------------
1961
+ // State mutations
1962
+ // -------------------------------------------------------------------------
1963
+ /** Marks a step as running */
1964
+ markStepRunning(stepId) {
1965
+ this._updateStep(stepId, { status: "running" });
1966
+ }
1967
+ /**
1968
+ * Marks a step as done, stores its output under `outputKey` (if set),
1969
+ * and advances `currentStepIndex` to the next pending step.
1970
+ */
1971
+ markStepDone(stepId, output) {
1972
+ const step = this.plan.steps.find((s) => s.stepId === stepId);
1973
+ if (step?.outputKey && output !== void 0) {
1974
+ this.outputs.set(step.outputKey, output);
1975
+ }
1976
+ this._updateStep(stepId, { status: "done" });
1977
+ this._advanceIndex();
1978
+ }
1979
+ /**
1980
+ * Marks a step as failed and propagates `skipped` to all transitively dependent steps.
1981
+ */
1982
+ markStepFailed(stepId) {
1983
+ this._updateStep(stepId, { status: "failed" });
1984
+ this._skipDependants(stepId);
1985
+ }
1986
+ /**
1987
+ * Marks a step as skipped (e.g., condition not met) and propagates to its dependants.
1988
+ */
1989
+ markStepSkipped(stepId) {
1990
+ this._updateStep(stepId, { status: "skipped" });
1991
+ this._skipDependants(stepId);
1992
+ }
1993
+ // -------------------------------------------------------------------------
1994
+ // Output / input mapping helpers
1995
+ // -------------------------------------------------------------------------
1996
+ /** Retrieves an output stored by `outputKey` from a completed step */
1997
+ getOutput(key) {
1998
+ return this.outputs.get(key);
1999
+ }
2000
+ /**
2001
+ * Resolves the `inputMapping` for a step into a concrete parameter map,
2002
+ * substituting `outputKey` values from prior steps. Static (non-key) values
2003
+ * in the mapping are passed through unchanged.
2004
+ */
2005
+ resolveInputs(step, baseArgs = {}) {
2006
+ if (!step.inputMapping) return baseArgs;
2007
+ const resolved = { ...baseArgs };
2008
+ for (const [paramName, outputKey] of Object.entries(step.inputMapping)) {
2009
+ const value = this.outputs.get(outputKey);
2010
+ if (value !== void 0) {
2011
+ resolved[paramName] = value;
2012
+ } else {
2013
+ resolved[paramName] = outputKey;
2014
+ }
2015
+ }
2016
+ return resolved;
2017
+ }
2018
+ // -------------------------------------------------------------------------
2019
+ // Internals
2020
+ // -------------------------------------------------------------------------
2021
+ _updateStep(stepId, patch) {
2022
+ this.plan.steps = this.plan.steps.map(
2023
+ (s) => s.stepId === stepId ? { ...s, ...patch } : s
2024
+ );
2025
+ }
2026
+ _skipDependants(failedStepId) {
2027
+ const toSkip = /* @__PURE__ */ new Set([failedStepId]);
2028
+ let changed = true;
2029
+ while (changed) {
2030
+ changed = false;
2031
+ for (const step of this.plan.steps) {
2032
+ if (step.status === "pending" && step.dependsOn.some((d) => toSkip.has(d))) {
2033
+ this._updateStep(step.stepId, { status: "skipped" });
2034
+ toSkip.add(step.stepId);
2035
+ changed = true;
2036
+ }
2037
+ }
2038
+ }
2039
+ }
2040
+ _advanceIndex() {
2041
+ const nextPending = this.plan.steps.findIndex(
2042
+ (s, i) => i > this.plan.currentStepIndex && s.status === "pending"
2043
+ );
2044
+ if (nextPending !== -1) {
2045
+ this.plan.currentStepIndex = nextPending;
2046
+ }
1555
2047
  }
1556
2048
  };
1557
2049
  var MCPClient = class {
@@ -2024,6 +2516,9 @@ var SessionManager = class {
2024
2516
  iterations = 0;
2025
2517
  logger;
2026
2518
  media;
2519
+ sessionId;
2520
+ scratchpadLoaded = false;
2521
+ pendingScratchpadClear = false;
2027
2522
  // Advanced execution state
2028
2523
  stepCounter;
2029
2524
  toolResponseProcessor;
@@ -2031,7 +2526,7 @@ var SessionManager = class {
2031
2526
  continuationPlanner = null;
2032
2527
  // MCP
2033
2528
  mcpRegistry = null;
2034
- /** Resolves when all MCP servers are connected; used by run() and stream() */
2529
+ /** Resolves when all MCP servers are connected; awaited by run() and stream() */
2035
2530
  mcpReady = null;
2036
2531
  // Tool execution budget tracking
2037
2532
  totalToolCallCount = 0;
@@ -2041,14 +2536,23 @@ var SessionManager = class {
2041
2536
  this.config = config;
2042
2537
  this.adapter = config.adapter;
2043
2538
  this.logger = config.logger || new DefaultLogger();
2539
+ this.sessionId = config.sessionId || "default";
2044
2540
  this.contextManager = new ContextManager();
2045
2541
  this.toolRegistry = new ToolRegistry(config.tools || [], {
2046
2542
  defaultTimeoutMs: config.toolRegistryTimeoutMs ?? 3e4
2047
2543
  });
2048
2544
  this.skillInjector = new SkillInjector(config.skills || []);
2545
+ if (config.activeDynamicSkills && config.activeDynamicSkills.length > 0) {
2546
+ for (const name of config.activeDynamicSkills) {
2547
+ this.skillInjector.enableSkill(name);
2548
+ }
2549
+ }
2550
+ if (config.activeDynamicTags && config.activeDynamicTags.length > 0) {
2551
+ this.skillInjector.enableByTags(config.activeDynamicTags);
2552
+ }
2049
2553
  this.media = new MediaBridge(this.adapter);
2050
2554
  this.stepCounter = new StepCounter(config.maxSteps ?? 20);
2051
- this.toolResponseProcessor = config.toolResponseProcessor ?? new ToolResponseProcessor();
2555
+ this.toolResponseProcessor = config.toolResponseProcessor instanceof ToolResponseProcessor ? config.toolResponseProcessor : config.toolResponseProcessor ? config.toolResponseProcessor : new ToolResponseProcessor();
2052
2556
  for (const strategy of config.compressionStrategies || []) {
2053
2557
  this.contextManager.registerStrategy(strategy);
2054
2558
  }
@@ -2076,11 +2580,11 @@ var SessionManager = class {
2076
2580
  maxTokens: config.maxTokens,
2077
2581
  metadata: {}
2078
2582
  };
2079
- this.goalInjector = null;
2080
2583
  if (config.mcpServers && config.mcpServers.length > 0) {
2081
2584
  this.mcpRegistry = new MCPClientRegistry(this.logger);
2082
2585
  this.mcpReady = this._initMCP(config.mcpServers);
2083
2586
  }
2587
+ const activeSkills = this.skillInjector.getActiveSkills();
2084
2588
  this.emitTrace("system", "session_init", {
2085
2589
  config: {
2086
2590
  model: this.config.model,
@@ -2089,9 +2593,29 @@ var SessionManager = class {
2089
2593
  parallelToolCalls: this.config.parallelToolCalls,
2090
2594
  enableGoalPlanning: this.config.enableGoalPlanning,
2091
2595
  enableContinuationPlanning: this.config.enableContinuationPlanning
2596
+ },
2597
+ skills: {
2598
+ total: (config.skills || []).length,
2599
+ active: activeSkills.length,
2600
+ fixed: activeSkills.filter((s) => s.strategy !== "dynamic").length,
2601
+ dynamic: activeSkills.filter((s) => s.strategy === "dynamic").length
2092
2602
  }
2093
2603
  });
2604
+ for (const skill of activeSkills) {
2605
+ this.emitTrace("skill", "skill_load", {
2606
+ name: skill.name,
2607
+ version: skill.version,
2608
+ strategy: skill.strategy ?? "fixed",
2609
+ inject: skill.inject,
2610
+ priority: skill.priority,
2611
+ tags: skill.tags ?? [],
2612
+ requiredTools: skill.requiredTools ?? []
2613
+ });
2614
+ }
2094
2615
  }
2616
+ // -----------------------------------------------------------------------
2617
+ // Trace helper
2618
+ // -----------------------------------------------------------------------
2095
2619
  emitTrace(type, name, metadata, input, output, status = "done") {
2096
2620
  if (this.config.onTrace) {
2097
2621
  this.config.onTrace({
@@ -2105,24 +2629,172 @@ var SessionManager = class {
2105
2629
  });
2106
2630
  }
2107
2631
  }
2108
- /**
2109
- * Returns a shallow copy of the current context window.
2110
- */
2632
+ async ensureScratchpadLoaded() {
2633
+ if (!this.config.scratchpadAdapter) return;
2634
+ if (this.pendingScratchpadClear) {
2635
+ await this.config.scratchpadAdapter.clear(this.sessionId);
2636
+ this.context.scratchpad = "";
2637
+ this.pendingScratchpadClear = false;
2638
+ this.scratchpadLoaded = true;
2639
+ return;
2640
+ }
2641
+ if (this.scratchpadLoaded) return;
2642
+ const stored = await this.config.scratchpadAdapter.read(this.sessionId);
2643
+ this.context.scratchpad = stored ?? "";
2644
+ this.scratchpadLoaded = true;
2645
+ }
2646
+ // -----------------------------------------------------------------------
2647
+ // Public accessors
2648
+ // -----------------------------------------------------------------------
2649
+ /** Returns a shallow copy of the current context window. */
2111
2650
  getContext() {
2112
2651
  return { ...this.context };
2113
2652
  }
2114
- /**
2115
- * Returns the current conversation history.
2116
- */
2653
+ /** Returns the current conversation history. */
2117
2654
  getHistory() {
2118
2655
  return [...this.context.turns];
2119
2656
  }
2120
2657
  /**
2121
- * Returns the `MediaBridge` for direct ASR / TTS / Vision / Image-gen calls.
2658
+ * Populates the session context with a pre-existing conversation history.
2659
+ *
2660
+ * Turns are assigned sequential `turnIndex` values starting from 0 and the
2661
+ * context `tokenCount` is recalculated automatically. Call this immediately
2662
+ * after construction and before the first `run()` / `stream()`.
2663
+ *
2664
+ * @param history - Raw history entries (role + content, optional toolCalls/toolResults).
2665
+ *
2666
+ * @example
2667
+ * ```typescript
2668
+ * const session = new SessionManager(config);
2669
+ * session.loadHistory(savedMessages);
2670
+ * const answer = await session.run('Continue where we left off.');
2671
+ * ```
2122
2672
  */
2673
+ loadHistory(history) {
2674
+ this.context.turns = history.map((m, i) => ({
2675
+ role: m.role,
2676
+ content: m.content ?? "",
2677
+ tokenCount: this.adapter.estimateTokens(
2678
+ typeof m.content === "string" ? m.content : JSON.stringify(m.content)
2679
+ ),
2680
+ turnIndex: i,
2681
+ compressed: false,
2682
+ ...m.toolCalls ? { toolCalls: m.toolCalls } : {},
2683
+ ...m.toolResults ? { toolResults: m.toolResults } : {}
2684
+ }));
2685
+ this.context.tokenCount = this.context.turns.reduce((sum, t) => sum + t.tokenCount, 0) + this.adapter.estimateTokens(this.context.systemPrompt || "");
2686
+ this.emitTrace("system", "history_loaded", { turnCount: this.context.turns.length });
2687
+ }
2688
+ /** Returns the `MediaBridge` for direct ASR / TTS / Vision / Image-gen calls. */
2123
2689
  getMedia() {
2124
2690
  return this.media;
2125
2691
  }
2692
+ /**
2693
+ * Returns the `ToolRegistry` for runtime tool management.
2694
+ *
2695
+ * Use this to register or unregister tools after session construction:
2696
+ *
2697
+ * ```typescript
2698
+ * // Add a tool after the user authenticates
2699
+ * session.tools.register(paymentTool);
2700
+ *
2701
+ * // Remove a tool when no longer needed
2702
+ * session.tools.unregister('send_payment');
2703
+ *
2704
+ * // Inspect what's registered
2705
+ * const active = session.tools.getAll();
2706
+ * console.log(active.map(t => t.name));
2707
+ * ```
2708
+ *
2709
+ * @since 1.4.0
2710
+ */
2711
+ get tools() {
2712
+ return this.toolRegistry;
2713
+ }
2714
+ /**
2715
+ * Returns the `SkillInjector` for runtime skill management.
2716
+ *
2717
+ * Use this to enable or disable dynamic skills after session construction:
2718
+ *
2719
+ * ```typescript
2720
+ * session.skills.enableSkill('code-review');
2721
+ * session.skills.enableByTags(['debugging']);
2722
+ * session.skills.disableSkill('verbose-mode');
2723
+ * const requiredTools = session.skills.getRequiredTools();
2724
+ * ```
2725
+ *
2726
+ * @since 1.4.0
2727
+ */
2728
+ get skills() {
2729
+ return this.skillInjector;
2730
+ }
2731
+ // -----------------------------------------------------------------------
2732
+ // Continuation planning API
2733
+ // -----------------------------------------------------------------------
2734
+ /**
2735
+ * Sets an explicit multi-step continuation plan that will be tracked and
2736
+ * injected as a status block before each ReAct iteration.
2737
+ *
2738
+ * Dependency tracking, condition evaluation, `outputKey` storage, and
2739
+ * `inputMapping` resolution are all handled automatically by the planner.
2740
+ *
2741
+ * @param steps - The ordered list of continuation steps.
2742
+ * @param strategy - Execution strategy ('sequential' | 'parallel' | 'conditional'). Default: 'sequential'.
2743
+ *
2744
+ * @example
2745
+ * ```typescript
2746
+ * await session.setPlan([
2747
+ * { stepId: 'fetch', toolName: 'fetch_data', description: 'Get data', dependsOn: [], outputKey: 'rawData' },
2748
+ * { stepId: 'analyze', toolName: 'analyze', description: 'Analyze', dependsOn: ['fetch'], inputMapping: { data: 'rawData' } },
2749
+ * ]);
2750
+ * const result = await session.run('Run the data pipeline.');
2751
+ * ```
2752
+ */
2753
+ setPlan(steps, strategy = "sequential") {
2754
+ this.continuationPlanner = new ContinuationPlanner({
2755
+ steps,
2756
+ currentStepIndex: 0,
2757
+ strategy
2758
+ });
2759
+ this.context.metadata["continuationPlan"] = this.continuationPlanner.getPlan();
2760
+ this.logger.debug(`[ContinuationPlanner] Plan set with ${steps.length} steps (strategy: ${strategy})`);
2761
+ this.emitTrace("planning", "plan_set", { stepCount: steps.length, strategy });
2762
+ }
2763
+ // -----------------------------------------------------------------------
2764
+ // Goal planning API
2765
+ // -----------------------------------------------------------------------
2766
+ /**
2767
+ * Manually sets the agent's goal, bypassing the automatic mini-planning LLM call.
2768
+ *
2769
+ * Use this when you already know the goal structure upfront.
2770
+ *
2771
+ * @example
2772
+ * ```typescript
2773
+ * await session.setGoal({
2774
+ * statement: 'Audit the authentication module',
2775
+ * decomposition: ['Read src/auth/', 'Identify SQL injection risks', 'Write report'],
2776
+ * successCriteria: ['Report covers all audit areas', 'Each finding has a severity rating'],
2777
+ * });
2778
+ * const result = await session.run('Begin the security audit.');
2779
+ * ```
2780
+ */
2781
+ setGoal(goal) {
2782
+ this.goalInjector = new GoalInjector({
2783
+ id: "manual",
2784
+ statement: goal.statement,
2785
+ decomposition: goal.decomposition ?? [],
2786
+ successCriteria: goal.successCriteria ?? [],
2787
+ injectionFrequency: this.config.goalInjectionFrequency ?? "always",
2788
+ injectionPosition: this.config.goalInjectionPosition ?? "system_prompt",
2789
+ completedSubGoals: goal.completedSubGoals ?? []
2790
+ });
2791
+ this.context.metadata["goal"] = this.goalInjector.getGoal();
2792
+ this.logger.debug("[GoalInjector] Goal set manually");
2793
+ this.emitTrace("planning", "goal_set_manual", {
2794
+ statement: goal.statement,
2795
+ subGoals: goal.decomposition?.length ?? 0
2796
+ });
2797
+ }
2126
2798
  // -----------------------------------------------------------------------
2127
2799
  // MCP initialisation
2128
2800
  // -----------------------------------------------------------------------
@@ -2160,41 +2832,83 @@ var SessionManager = class {
2160
2832
  });
2161
2833
  }
2162
2834
  // -----------------------------------------------------------------------
2835
+ // Goal mini-planning step (one extra LLM call, gated by enableGoalPlanning)
2836
+ // -----------------------------------------------------------------------
2837
+ /**
2838
+ * Runs a dedicated planning prompt against the LLM to decompose the user's
2839
+ * message into sub-goals and success criteria. Called once at the start of
2840
+ * the first `run()` when `enableGoalPlanning` is true and no goal has been
2841
+ * manually set via `setGoal()`.
2842
+ */
2843
+ async _runMiniPlanningStep(userMessage) {
2844
+ const planningPrompt = `Given this goal: "${userMessage}"
2845
+
2846
+ 1. List the sub-goals needed to achieve this (max 5, be specific)
2847
+ 2. List success criteria \u2014 what does "done" look like? (max 3, binary, measurable)
2848
+
2849
+ Respond ONLY with valid JSON (no markdown, no explanations):
2850
+ { "subGoals": string[], "successCriteria": string[] }`;
2851
+ try {
2852
+ const response = await this.adapter.complete({
2853
+ model: this.config.model,
2854
+ messages: [{ role: "user", content: planningPrompt }],
2855
+ maxTokens: this.config.maxCompletionTokens ?? 2e3
2856
+ });
2857
+ const raw = response.content.replace(/```json|```/g, "").trim();
2858
+ const parsed = JSON.parse(raw);
2859
+ if (this.goalInjector && Array.isArray(parsed.subGoals)) {
2860
+ this.goalInjector.updateDecomposition(
2861
+ parsed.subGoals,
2862
+ Array.isArray(parsed.successCriteria) ? parsed.successCriteria : void 0
2863
+ );
2864
+ this.context.metadata["goal"] = this.goalInjector.getGoal();
2865
+ this.emitTrace("planning", "mini_plan_done", {
2866
+ subGoals: parsed.subGoals,
2867
+ successCriteria: parsed.successCriteria
2868
+ });
2869
+ this.logger.debug(`[GoalInjector] Mini-plan: ${parsed.subGoals.length} sub-goals`);
2870
+ }
2871
+ } catch (err) {
2872
+ this.logger.warn(`[GoalInjector] Mini-planning step failed: ${err.message ?? String(err)}`);
2873
+ }
2874
+ }
2875
+ // -----------------------------------------------------------------------
2163
2876
  // Internal helpers
2164
2877
  // -----------------------------------------------------------------------
2165
2878
  /** Builds the system prompt, injecting skills and goal if configured. */
2166
- buildSystemPrompt(userMessage) {
2879
+ buildSystemPrompt(userMessage, iteration = 0) {
2167
2880
  let prompt = this.context.systemPrompt || "";
2168
- if (this.config.enableGoalPlanning && userMessage) {
2169
- if (!this.goalInjector) {
2170
- this.goalInjector = new GoalInjector({
2171
- id: "main",
2172
- statement: typeof userMessage === "string" ? userMessage : "[multimodal]",
2173
- decomposition: [],
2174
- successCriteria: ["The user request is fully answered"],
2175
- injectionFrequency: this.config.goalInjectionFrequency ?? "always",
2176
- injectionPosition: this.config.goalInjectionPosition ?? "system_prompt"
2177
- });
2178
- this.logger.debug("Goal injector initialised");
2179
- const goal = this.goalInjector.getGoal();
2180
- this.emitTrace("planning", "goal_init", {
2181
- statement: goal.statement,
2182
- criteria: goal.successCriteria
2183
- });
2184
- }
2185
- if (this.config.goalInjectionPosition !== "pre_turn") {
2881
+ if (this.goalInjector && this.config.goalInjectionPosition !== "pre_turn") {
2882
+ const shouldInject = this.goalInjector.shouldInjectThisTurn(
2883
+ iteration,
2884
+ false,
2885
+ this.config.goalInjectionN ?? 3
2886
+ );
2887
+ if (shouldInject) {
2186
2888
  prompt = this.goalInjector.injectInto(prompt);
2187
- this.emitTrace("planning", "goal_injected", { position: this.config.goalInjectionPosition || "system_prompt" });
2889
+ this.emitTrace("planning", "goal_injected", {
2890
+ position: "system_prompt",
2891
+ iteration
2892
+ });
2188
2893
  }
2189
2894
  }
2190
- const injectedSkills = this.skillInjector.buildInjectionBlock("system_prompt");
2895
+ if (this.continuationPlanner && this.config.enableContinuationPlanning) {
2896
+ const planStatus = this.continuationPlanner.getPlanStatusString();
2897
+ prompt += `
2898
+
2899
+ ${planStatus}`;
2900
+ }
2901
+ const injectedSkills = this.skillInjector.buildInjectionBlock(
2902
+ "system_prompt",
2903
+ this.config.skillTokenBudget
2904
+ );
2191
2905
  if (injectedSkills) {
2192
2906
  prompt += "\n\n" + injectedSkills;
2193
2907
  }
2194
2908
  return prompt.trim();
2195
2909
  }
2196
- /** Builds the messages array for the provider from the current context */
2197
- buildMessages(systemPrompt) {
2910
+ /** Builds the messages array for the provider from the current context. */
2911
+ buildMessages(systemPrompt, iteration = 0) {
2198
2912
  const messages = this.context.turns.map((t) => ({
2199
2913
  role: t.role,
2200
2914
  content: t.content,
@@ -2205,13 +2919,20 @@ var SessionManager = class {
2205
2919
  messages.unshift({ role: "system", content: systemPrompt });
2206
2920
  }
2207
2921
  if (this.goalInjector && this.config.goalInjectionPosition === "pre_turn") {
2208
- const goalBlock = this.goalInjector.injectInto("");
2209
- messages.push({ role: "system", content: goalBlock });
2210
- this.emitTrace("planning", "goal_injected", { position: "pre_turn" });
2922
+ const shouldInject = this.goalInjector.shouldInjectThisTurn(
2923
+ iteration,
2924
+ false,
2925
+ this.config.goalInjectionN ?? 3
2926
+ );
2927
+ if (shouldInject) {
2928
+ const goalBlock = this.goalInjector.getFormattedBlock();
2929
+ messages.push({ role: "system", content: goalBlock });
2930
+ this.emitTrace("planning", "goal_injected", { position: "pre_turn", iteration });
2931
+ }
2211
2932
  }
2212
2933
  return messages;
2213
2934
  }
2214
- /** Checks the tool execution budget and throws descriptively if exceeded */
2935
+ /** Checks the tool execution budget and throws descriptively if exceeded. */
2215
2936
  checkExecutionBudget(toolName) {
2216
2937
  const budget = this.config.toolExecutionBudget;
2217
2938
  if (!budget) return;
@@ -2229,7 +2950,11 @@ var SessionManager = class {
2229
2950
  `Tool execution budget exceeded: '${toolName}' has reached its per-tool limit of ${budget.maxCallsPerTool[toolName]}`
2230
2951
  );
2231
2952
  this.logger.warn(err.message);
2232
- this.emitTrace("budget", "tool_limit_exceeded", { toolName, limit: budget.maxCallsPerTool[toolName], totalTokens: this.totalTokens });
2953
+ this.emitTrace("budget", "tool_limit_exceeded", {
2954
+ toolName,
2955
+ limit: budget.maxCallsPerTool[toolName],
2956
+ totalTokens: this.totalTokens
2957
+ });
2233
2958
  throw err;
2234
2959
  }
2235
2960
  }
@@ -2240,11 +2965,80 @@ var SessionManager = class {
2240
2965
  tokenBudgetRemaining: this.config.maxTokens - this.totalTokens
2241
2966
  });
2242
2967
  }
2243
- /** Records a tool call in budget counters */
2968
+ /** Records a tool call in budget counters. */
2244
2969
  recordToolCall(toolName) {
2245
2970
  this.totalToolCallCount++;
2246
2971
  this.perToolCallCount.set(toolName, (this.perToolCallCount.get(toolName) ?? 0) + 1);
2247
2972
  }
2973
+ // -----------------------------------------------------------------------
2974
+ // Blob detection helpers
2975
+ // -----------------------------------------------------------------------
2976
+ isProbablyBase64(value) {
2977
+ const v = value.trim();
2978
+ if (v.startsWith("data:") && v.includes(";base64,")) return true;
2979
+ if (v.length < 4096) return false;
2980
+ if (/[^A-Za-z0-9+/=]/.test(v)) return false;
2981
+ return true;
2982
+ }
2983
+ isBinaryLike(value) {
2984
+ if (!value) return false;
2985
+ if (typeof ArrayBuffer !== "undefined") {
2986
+ if (value instanceof ArrayBuffer) return true;
2987
+ if (ArrayBuffer.isView(value)) return true;
2988
+ }
2989
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(value)) return true;
2990
+ return false;
2991
+ }
2992
+ async storeBlob(content, metadata) {
2993
+ if (!this.config.stmRegistry) return null;
2994
+ return this.config.stmRegistry.register(content, "blob", metadata);
2995
+ }
2996
+ async scrubBlobFields(value, toolName, depth = 0) {
2997
+ if (!value || typeof value !== "object") return { value, changed: false };
2998
+ if (depth > 2) return { value, changed: false };
2999
+ if (Array.isArray(value)) {
3000
+ let changed2 = false;
3001
+ const out = [];
3002
+ for (const item of value) {
3003
+ const res = await this.scrubBlobFields(item, toolName, depth + 1);
3004
+ out.push(res.value);
3005
+ if (res.changed) changed2 = true;
3006
+ }
3007
+ return { value: out, changed: changed2 };
3008
+ }
3009
+ const obj = { ...value };
3010
+ let changed = false;
3011
+ for (const [key, v] of Object.entries(obj)) {
3012
+ if (typeof v === "string" && this.isProbablyBase64(v)) {
3013
+ const ref = await this.storeBlob(v, {
3014
+ toolName,
3015
+ key,
3016
+ encoding: v.startsWith("data:") ? "data_url" : "base64"
3017
+ });
3018
+ if (ref) {
3019
+ obj[key] = ref;
3020
+ obj[`${key}Note`] = "Stored in STM";
3021
+ changed = true;
3022
+ }
3023
+ continue;
3024
+ }
3025
+ if (this.isBinaryLike(v)) {
3026
+ const ref = await this.storeBlob(v, { toolName, key });
3027
+ if (ref) {
3028
+ obj[key] = ref;
3029
+ obj[`${key}Note`] = "Stored in STM";
3030
+ changed = true;
3031
+ }
3032
+ continue;
3033
+ }
3034
+ if (typeof v === "object" && v !== null) {
3035
+ const nested = await this.scrubBlobFields(v, toolName, depth + 1);
3036
+ obj[key] = nested.value;
3037
+ if (nested.changed) changed = true;
3038
+ }
3039
+ }
3040
+ return { value: obj, changed };
3041
+ }
2248
3042
  /**
2249
3043
  * Processes a firewall decision for a tool call.
2250
3044
  * Returns true to proceed, false to block.
@@ -2279,24 +3073,53 @@ var SessionManager = class {
2279
3073
  }
2280
3074
  /**
2281
3075
  * Executes a single parsed tool call and returns the serialised result string.
3076
+ * Also handles continuation plan tracking (step status + output storage).
2282
3077
  */
2283
3078
  async executeSingleToolCall(tc) {
2284
3079
  this.checkExecutionBudget(tc.name);
2285
- const args = JSON.parse(tc.arguments);
3080
+ let args = JSON.parse(tc.arguments);
3081
+ if (this.continuationPlanner) {
3082
+ const plan = this.continuationPlanner.getPlan();
3083
+ const matchingStep = plan.steps.find(
3084
+ (s) => s.toolName === tc.name && s.status === "pending"
3085
+ );
3086
+ if (matchingStep) {
3087
+ this.continuationPlanner.markStepRunning(matchingStep.stepId);
3088
+ args = this.continuationPlanner.resolveInputs(matchingStep, args);
3089
+ }
3090
+ }
2286
3091
  const executeContext = {
2287
- sessionId: "default",
3092
+ sessionId: this.sessionId,
2288
3093
  turnIndex: this.context.turns.length,
2289
3094
  logger: this.logger,
2290
3095
  adapter: this.adapter,
2291
3096
  stmRegistry: this.config.stmRegistry,
2292
- scratchpad: this.context.scratchpad
3097
+ scratchpad: this.context.scratchpad,
3098
+ scratchpadAdapter: this.config.scratchpadAdapter
2293
3099
  };
2294
3100
  if (this.config.ragAdapter) {
2295
3101
  executeContext["ragAdapter"] = this.config.ragAdapter;
2296
3102
  }
2297
- this.logger.debug(`Executing tool: ${tc.name}`, { args: tc.arguments });
2298
- this.emitTrace("tool_call", tc.name, { id: tc.id }, tc.arguments, null, "running");
2299
- const result = await this.toolRegistry.execute(tc.name, args, executeContext);
3103
+ this.logger.debug(`Executing tool: ${tc.name}`, { args: JSON.stringify(args) });
3104
+ this.emitTrace("tool_call", tc.name, { id: tc.id }, JSON.stringify(args), null, "running");
3105
+ let result;
3106
+ let executionError = null;
3107
+ try {
3108
+ result = await this.toolRegistry.execute(tc.name, args, executeContext);
3109
+ } catch (err) {
3110
+ executionError = err;
3111
+ if (this.continuationPlanner) {
3112
+ const plan = this.continuationPlanner.getPlan();
3113
+ const runningStep = plan.steps.find(
3114
+ (s) => s.toolName === tc.name && s.status === "running"
3115
+ );
3116
+ if (runningStep) {
3117
+ this.continuationPlanner.markStepFailed(runningStep.stepId);
3118
+ this.context.metadata["continuationPlan"] = this.continuationPlanner.getPlan();
3119
+ }
3120
+ }
3121
+ throw executionError;
3122
+ }
2300
3123
  this.recordToolCall(tc.name);
2301
3124
  this.logger.debug(`Tool ${tc.name} returned successfully`);
2302
3125
  let finalResult = result;
@@ -2308,6 +3131,22 @@ var SessionManager = class {
2308
3131
  this.emitTrace("planning", "scratchpad_update", { note: resObj["note"] });
2309
3132
  }
2310
3133
  }
3134
+ if (this.config.stmRegistry) {
3135
+ if (typeof finalResult === "string" && this.isProbablyBase64(finalResult)) {
3136
+ const ref = await this.storeBlob(finalResult, { toolName: tc.name, encoding: "base64" });
3137
+ if (ref) {
3138
+ finalResult = { blobRef: ref, note: "Binary content stored in STM" };
3139
+ }
3140
+ } else if (this.isBinaryLike(finalResult)) {
3141
+ const ref = await this.storeBlob(finalResult, { toolName: tc.name });
3142
+ if (ref) {
3143
+ finalResult = { blobRef: ref, note: "Binary content stored in STM" };
3144
+ }
3145
+ } else if (typeof finalResult === "object" && finalResult !== null) {
3146
+ const scrubbed = await this.scrubBlobFields(finalResult, tc.name);
3147
+ if (scrubbed.changed) finalResult = scrubbed.value;
3148
+ }
3149
+ }
2311
3150
  let content = JSON.stringify(finalResult);
2312
3151
  const tokenCount = this.adapter.estimateTokens(content);
2313
3152
  if (this.config.maxTokensPerTool && tokenCount > this.config.maxTokensPerTool) {
@@ -2324,10 +3163,32 @@ var SessionManager = class {
2324
3163
  content = this.toolResponseProcessor.compress(content, evaluation);
2325
3164
  this.emitTrace("compression", tc.name, { originalSize: evaluation.sizeClass }, null, content);
2326
3165
  }
2327
- if (this.config.enableContinuationPlanning && evaluation.suggestedAction === "continue") {
2328
- this.emitTrace("planning", "continuation_detected", { toolName: tc.name, action: evaluation.suggestedAction });
3166
+ if (this.continuationPlanner) {
3167
+ const plan = this.continuationPlanner.getPlan();
3168
+ const runningStep = plan.steps.find(
3169
+ (s) => s.toolName === tc.name && s.status === "running"
3170
+ );
3171
+ if (runningStep) {
3172
+ this.continuationPlanner.markStepDone(runningStep.stepId, content);
3173
+ if (runningStep.outputKey) {
3174
+ const outputs = this.context.metadata["toolOutputs"] ?? {};
3175
+ outputs[runningStep.outputKey] = content;
3176
+ this.context.metadata["toolOutputs"] = outputs;
3177
+ }
3178
+ this.context.metadata["continuationPlan"] = this.continuationPlanner.getPlan();
3179
+ this.emitTrace("planning", "step_done", {
3180
+ stepId: runningStep.stepId,
3181
+ outputKey: runningStep.outputKey
3182
+ });
3183
+ }
3184
+ }
3185
+ if (evaluation.suggestedAction === "continue" && this.config.enableContinuationPlanning) {
3186
+ this.emitTrace("planning", "continuation_detected", {
3187
+ toolName: tc.name,
3188
+ action: evaluation.suggestedAction
3189
+ });
2329
3190
  }
2330
- this.emitTrace("tool_result", tc.name, { id: tc.id }, tc.arguments, content, "done");
3191
+ this.emitTrace("tool_result", tc.name, { id: tc.id }, JSON.stringify(args), content, "done");
2331
3192
  return content;
2332
3193
  }
2333
3194
  // -----------------------------------------------------------------------
@@ -2336,18 +3197,38 @@ var SessionManager = class {
2336
3197
  /**
2337
3198
  * Runs the full ReAct loop for a user message and returns the final assistant response.
2338
3199
  *
3200
+ * When `enableGoalPlanning` is true and no goal has been manually set, a mini-planning
3201
+ * LLM call is made before the first iteration to decompose the task into sub-goals.
3202
+ *
2339
3203
  * @param userMessage - The user's message (string or multimodal content blocks)
2340
3204
  * @returns The assistant's final response string
2341
3205
  * @throws {LemuraMaxIterationsError} When the loop exceeds `maxIterations`
2342
3206
  */
2343
3207
  async run(userMessage) {
2344
3208
  if (this.mcpReady) await this.mcpReady;
3209
+ await this.ensureScratchpadLoaded();
2345
3210
  const userMessageStr = Array.isArray(userMessage) ? "[Multimodal Content]" : userMessage;
2346
3211
  this.logger.info(`Starting new session run`, {
2347
3212
  model: this.config.model,
2348
3213
  message: userMessageStr
2349
3214
  });
2350
- const systemPrompt = this.buildSystemPrompt(userMessageStr);
3215
+ if (this.config.enableGoalPlanning && !this.goalInjector) {
3216
+ this.goalInjector = new GoalInjector({
3217
+ id: "auto",
3218
+ statement: typeof userMessage === "string" ? userMessage : "[multimodal]",
3219
+ decomposition: [],
3220
+ successCriteria: ["The user request is fully answered"],
3221
+ injectionFrequency: this.config.goalInjectionFrequency ?? "always",
3222
+ injectionPosition: this.config.goalInjectionPosition ?? "system_prompt"
3223
+ });
3224
+ this.context.metadata["goal"] = this.goalInjector.getGoal();
3225
+ this.logger.debug("Goal injector initialised (auto)");
3226
+ this.emitTrace("planning", "goal_init", {
3227
+ statement: this.goalInjector.getGoal().statement,
3228
+ criteria: this.goalInjector.getGoal().successCriteria
3229
+ });
3230
+ await this._runMiniPlanningStep(userMessageStr);
3231
+ }
2351
3232
  this.context.turns.push({
2352
3233
  role: "user",
2353
3234
  content: userMessage,
@@ -2358,18 +3239,24 @@ var SessionManager = class {
2358
3239
  const maxIts = this.config.maxIterations || 10;
2359
3240
  this.iterations = 0;
2360
3241
  this.stepCounter = new StepCounter(this.config.maxSteps ?? 20);
3242
+ const maxCompletionTokens = this.config.maxCompletionTokens ?? 2e3;
2361
3243
  while (this.iterations < maxIts) {
2362
3244
  this.iterations++;
2363
3245
  this.logger.debug(`ReAct Iteration ${this.iterations}/${maxIts}`);
3246
+ this.context.tokenCount = this.context.turns.reduce((sum, t) => sum + t.tokenCount, 0) + this.adapter.estimateTokens(this.context.systemPrompt || "");
2364
3247
  this.context = await this.contextManager.prepare(this.context);
2365
- let messages = this.buildMessages(systemPrompt);
3248
+ const systemPrompt = this.buildSystemPrompt(userMessageStr, this.iterations);
3249
+ let messages = this.buildMessages(systemPrompt, this.iterations);
2366
3250
  if (this.stepCounter.isMaxReached()) {
2367
3251
  this.logger.warn(`maxSteps (${this.config.maxSteps ?? 20}) reached \u2014 forcing final response`);
2368
3252
  messages.push({
2369
3253
  role: "system",
2370
3254
  content: this.stepCounter.getForcedConclusionPrompt() + "\n\n" + FinalResponseFormatter.getRequiredStructure()
2371
3255
  });
2372
- this.emitTrace("planning", "max_steps_reached", { maxSteps: this.config.maxSteps, currentSteps: this.stepCounter.count });
3256
+ this.emitTrace("planning", "max_steps_reached", {
3257
+ maxSteps: this.config.maxSteps,
3258
+ currentSteps: this.stepCounter.count
3259
+ });
2373
3260
  }
2374
3261
  this.logger.debug(`Calling provider adapter (${this.adapter.name})...`);
2375
3262
  this.emitTrace("thinking", "llm_call", {
@@ -2383,7 +3270,7 @@ var SessionManager = class {
2383
3270
  model: this.config.model,
2384
3271
  messages,
2385
3272
  tools: this.stepCounter.isMaxReached() ? [] : this.toolRegistry.getAll(),
2386
- maxTokens: 1e3
3273
+ maxTokens: maxCompletionTokens
2387
3274
  });
2388
3275
  } catch (err) {
2389
3276
  const e = err;
@@ -2476,13 +3363,14 @@ var SessionManager = class {
2476
3363
  this.context.turns.push(toolTurn);
2477
3364
  if (this.config.onTurn) this.config.onTurn(toolTurn);
2478
3365
  }
3366
+ if (this.goalInjector) this.goalInjector.incrementTurn();
2479
3367
  continue;
2480
3368
  }
2481
3369
  if (response.finishReason === "stop" || response.finishReason === "max_tokens" || response.finishReason === "error") {
2482
3370
  const finalTurn = {
2483
3371
  role: "assistant",
2484
3372
  content: response.content,
2485
- tokenCount: response.usage.completionTokens,
3373
+ tokenCount: response.usage?.completionTokens ?? this.adapter.estimateTokens(response.content),
2486
3374
  turnIndex: this.context.turns.length,
2487
3375
  compressed: false
2488
3376
  };
@@ -2512,18 +3400,32 @@ var SessionManager = class {
2512
3400
  * @returns An `AsyncIterable<string>` of delta tokens from the final response
2513
3401
  *
2514
3402
  * @example
3403
+ * ```typescript
2515
3404
  * for await (const token of session.stream('Tell me a story')) {
2516
3405
  * process.stdout.write(token);
2517
3406
  * }
3407
+ * ```
2518
3408
  */
2519
3409
  async *stream(userMessage) {
2520
3410
  if (this.mcpReady) await this.mcpReady;
3411
+ await this.ensureScratchpadLoaded();
2521
3412
  const userMessageStr = Array.isArray(userMessage) ? "[Multimodal Content]" : userMessage;
2522
3413
  this.logger.info(`Starting streaming session run`, {
2523
3414
  model: this.config.model,
2524
3415
  message: userMessageStr
2525
3416
  });
2526
- const systemPrompt = this.buildSystemPrompt(userMessageStr);
3417
+ if (this.config.enableGoalPlanning && !this.goalInjector) {
3418
+ this.goalInjector = new GoalInjector({
3419
+ id: "auto",
3420
+ statement: typeof userMessage === "string" ? userMessage : "[multimodal]",
3421
+ decomposition: [],
3422
+ successCriteria: ["The user request is fully answered"],
3423
+ injectionFrequency: this.config.goalInjectionFrequency ?? "always",
3424
+ injectionPosition: this.config.goalInjectionPosition ?? "system_prompt"
3425
+ });
3426
+ this.context.metadata["goal"] = this.goalInjector.getGoal();
3427
+ await this._runMiniPlanningStep(userMessageStr);
3428
+ }
2527
3429
  this.context.turns.push({
2528
3430
  role: "user",
2529
3431
  content: userMessage,
@@ -2534,11 +3436,14 @@ var SessionManager = class {
2534
3436
  const maxIts = this.config.maxIterations || 10;
2535
3437
  this.iterations = 0;
2536
3438
  this.stepCounter = new StepCounter(this.config.maxSteps ?? 20);
3439
+ const maxCompletionTokens = this.config.maxCompletionTokens ?? 2e3;
2537
3440
  while (this.iterations < maxIts) {
2538
3441
  this.iterations++;
2539
3442
  this.logger.debug(`[stream] ReAct Iteration ${this.iterations}/${maxIts}`);
3443
+ this.context.tokenCount = this.context.turns.reduce((sum, t) => sum + t.tokenCount, 0) + this.adapter.estimateTokens(this.context.systemPrompt || "");
2540
3444
  this.context = await this.contextManager.prepare(this.context);
2541
- const messages = this.buildMessages(systemPrompt);
3445
+ const systemPrompt = this.buildSystemPrompt(userMessageStr, this.iterations);
3446
+ const messages = this.buildMessages(systemPrompt, this.iterations);
2542
3447
  if (this.stepCounter.isMaxReached()) {
2543
3448
  messages.push({
2544
3449
  role: "system",
@@ -2551,7 +3456,7 @@ var SessionManager = class {
2551
3456
  model: this.config.model,
2552
3457
  messages,
2553
3458
  tools: this.stepCounter.isMaxReached() ? [] : this.toolRegistry.getAll(),
2554
- maxTokens: 1e3
3459
+ maxTokens: maxCompletionTokens
2555
3460
  });
2556
3461
  } catch (err) {
2557
3462
  this.logger.fatal(`Provider call failed: ${err.message}`);
@@ -2594,17 +3499,20 @@ var SessionManager = class {
2594
3499
  this.context.turns.push(toolTurn);
2595
3500
  if (this.config.onTurn) this.config.onTurn(toolTurn);
2596
3501
  }
3502
+ if (this.goalInjector) this.goalInjector.incrementTurn();
2597
3503
  continue;
2598
3504
  }
3505
+ this.context.tokenCount = this.context.turns.reduce((sum, t) => sum + t.tokenCount, 0) + this.adapter.estimateTokens(this.context.systemPrompt || "");
2599
3506
  this.context = await this.contextManager.prepare(this.context);
2600
- const finalMessages = this.buildMessages(systemPrompt);
3507
+ const finalSystemPrompt = this.buildSystemPrompt(userMessageStr, this.iterations);
3508
+ const finalMessages = this.buildMessages(finalSystemPrompt, this.iterations);
2601
3509
  let accumulated = "";
2602
3510
  let finalFinishReason = void 0;
2603
3511
  let finalTokenCount = 0;
2604
3512
  for await (const chunk of this.adapter.stream({
2605
3513
  model: this.config.model,
2606
3514
  messages: finalMessages,
2607
- maxTokens: 1e3,
3515
+ maxTokens: maxCompletionTokens,
2608
3516
  stream: true
2609
3517
  })) {
2610
3518
  if (chunk.delta) {
@@ -2645,7 +3553,8 @@ var SessionManager = class {
2645
3553
  }
2646
3554
  /**
2647
3555
  * Resets the session: clears conversation history, resets iteration counters,
2648
- * and resets tool execution budgets. The adapter, config, and tools are retained.
3556
+ * tool execution budget tallies, and goal/plan state.
3557
+ * The adapter, config, compression strategies, and tools are retained.
2649
3558
  */
2650
3559
  reset() {
2651
3560
  this.context = {
@@ -2658,24 +3567,30 @@ var SessionManager = class {
2658
3567
  };
2659
3568
  this.iterations = 0;
2660
3569
  this.totalToolCallCount = 0;
3570
+ this.totalTokens = 0;
2661
3571
  this.perToolCallCount.clear();
2662
3572
  this.stepCounter = new StepCounter(this.config.maxSteps ?? 20);
2663
3573
  this.goalInjector = null;
3574
+ this.continuationPlanner = null;
3575
+ this.scratchpadLoaded = false;
3576
+ this.pendingScratchpadClear = !!this.config.scratchpadAdapter;
2664
3577
  this.logger.debug("Session reset");
2665
3578
  }
2666
3579
  /**
2667
3580
  * Closes the session and disconnects all MCP servers.
2668
3581
  *
2669
- * Call this when you are done with the session and have registered MCP servers,
2670
- * to ensure child processes are terminated and HTTP connections are released.
3582
+ * Call this when you are done with the session to ensure child processes are
3583
+ * terminated and HTTP connections are released.
2671
3584
  *
2672
3585
  * @example
3586
+ * ```typescript
2673
3587
  * const session = new SessionManager({ ..., mcpServers: [...] });
2674
3588
  * try {
2675
3589
  * await session.run('Hello');
2676
3590
  * } finally {
2677
3591
  * await session.close();
2678
3592
  * }
3593
+ * ```
2679
3594
  */
2680
3595
  async close() {
2681
3596
  if (this.mcpRegistry) {
@@ -2686,37 +3601,6 @@ var SessionManager = class {
2686
3601
  }
2687
3602
  };
2688
3603
 
2689
- // src/agent/execution/ContinuationPlanner.ts
2690
- var ContinuationPlanner = class {
2691
- constructor(plan) {
2692
- this.plan = plan;
2693
- }
2694
- getPlanStatusString() {
2695
- let result = `[CONTINUATION PLAN \u2014 Step ${this.plan.currentStepIndex + 1}/${this.plan.steps.length}]
2696
- `;
2697
- for (const step of this.plan.steps) {
2698
- const getIcon = (status) => {
2699
- switch (status) {
2700
- case "done":
2701
- return "\u2705";
2702
- case "running":
2703
- return "\u25B6";
2704
- case "failed":
2705
- return "\u274C";
2706
- case "skipped":
2707
- return "\u23ED";
2708
- default:
2709
- return "\u23F3";
2710
- }
2711
- };
2712
- const statusText = step.status === "pending" && step.dependsOn.length > 0 ? `Waiting on Step ${step.dependsOn.join(", ")}` : step.status.charAt(0).toUpperCase() + step.status.slice(1);
2713
- result += `${getIcon(step.status)} Step ${step.stepId} (${step.toolName}): ${statusText}
2714
- `;
2715
- }
2716
- return result;
2717
- }
2718
- };
2719
-
2720
- export { ContextManager, ContinuationPlanner, DefaultLogger, FinalResponseFormatter, GoalInjector, HistoryCompressionStrategy, InMemoryRAGAdapter, InMemoryStorageAdapter, LemuraAdapterError, LemuraContextOverflowError, LemuraError, LemuraMCPConnectionError, LemuraMCPError, LemuraMCPTimeoutError, LemuraMaxIterationsError, LemuraSkillInjectionError, LemuraToolNotFoundError, LemuraToolTimeoutError, LemuraToolValidationError, LogLevel, MCPClient, MCPClientRegistry, MediaBridge, OpenAICompatibleAdapter, SandwichCompressionStrategy, ScratchpadStrategy, SessionManager, ShortTermMemoryRegistry, SkillInjector, StepCounter, ToolRegistry, ToolResponseProcessor, evaluateToolFirewall, validateJsonSchema };
3604
+ export { ContextManager, ContinuationPlanner, DefaultLogger, FinalResponseFormatter, GoalInjector, HistoryCompressionStrategy, InMemoryRAGAdapter, InMemoryScratchpadAdapter, InMemoryStorageAdapter, LemuraAdapterError, LemuraContextOverflowError, LemuraError, LemuraMCPConnectionError, LemuraMCPError, LemuraMCPTimeoutError, LemuraMaxIterationsError, LemuraSkillInjectionError, LemuraToolNotFoundError, LemuraToolTimeoutError, LemuraToolValidationError, LogLevel, MCPClient, MCPClientRegistry, MediaBridge, OpenAICompatibleAdapter, SandwichCompressionStrategy, ScratchpadStrategy, SessionManager, ShortTermMemoryRegistry, SkillInjector, StepCounter, SummaryInjectionStrategy, ToolRegistry, ToolResponseProcessor, evaluateToolFirewall, validateJsonSchema };
2721
3605
  //# sourceMappingURL=index.mjs.map
2722
3606
  //# sourceMappingURL=index.mjs.map