wave-agent-sdk 0.19.7 → 0.19.9

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 (75) hide show
  1. package/builtin/plugins/sdd/.wave-plugin/plugin.json +8 -0
  2. package/builtin/plugins/sdd/hooks/hooks.json +14 -0
  3. package/builtin/plugins/sdd/scripts/session-start.js +24 -0
  4. package/builtin/plugins/sdd/scripts/spec-count.js +77 -0
  5. package/builtin/plugins/sdd/skills/specify/SKILL.md +48 -0
  6. package/builtin/plugins/sdd/skills/specify/templates/spec-template.md +47 -0
  7. package/dist/agent.d.ts +8 -0
  8. package/dist/agent.js +30 -10
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.js +1 -0
  11. package/dist/managers/aiManager.d.ts +18 -0
  12. package/dist/managers/aiManager.js +155 -46
  13. package/dist/managers/permissionManager.d.ts +7 -0
  14. package/dist/managers/permissionManager.js +102 -142
  15. package/dist/managers/pluginManager.d.ts +7 -0
  16. package/dist/managers/pluginManager.js +31 -0
  17. package/dist/managers/subagentManager.js +6 -0
  18. package/dist/prompts/index.d.ts +12 -1
  19. package/dist/prompts/index.js +133 -45
  20. package/dist/services/aiService.d.ts +1 -17
  21. package/dist/services/aiService.js +3 -85
  22. package/dist/services/configurationService.d.ts +6 -0
  23. package/dist/services/configurationService.js +31 -0
  24. package/dist/services/remoteSettingsService.js +2 -0
  25. package/dist/services/session.d.ts +3 -1
  26. package/dist/services/session.js +12 -4
  27. package/dist/services/taskManager.d.ts +1 -0
  28. package/dist/services/taskManager.js +34 -5
  29. package/dist/tools/editTool.js +24 -10
  30. package/dist/tools/enterWorktreeTool.js +2 -1
  31. package/dist/tools/grepTool.js +8 -2
  32. package/dist/tools/writeTool.js +36 -0
  33. package/dist/types/configuration.d.ts +5 -0
  34. package/dist/types/permissions.d.ts +0 -2
  35. package/dist/types/processes.d.ts +27 -0
  36. package/dist/types/workflow.d.ts +1 -1
  37. package/dist/utils/bashParser.d.ts +25 -0
  38. package/dist/utils/bashParser.js +103 -0
  39. package/dist/utils/configPaths.d.ts +4 -0
  40. package/dist/utils/configPaths.js +6 -0
  41. package/dist/utils/containerSetup.js +0 -9
  42. package/dist/utils/fileSearch.js +4 -2
  43. package/dist/utils/worktreeSession.d.ts +1 -1
  44. package/dist/utils/worktreeSession.js +1 -1
  45. package/dist/utils/worktreeUtils.d.ts +7 -1
  46. package/dist/utils/worktreeUtils.js +10 -4
  47. package/dist/workflow/types.d.ts +5 -0
  48. package/package.json +1 -1
  49. package/src/agent.ts +29 -10
  50. package/src/index.ts +1 -0
  51. package/src/managers/aiManager.ts +219 -61
  52. package/src/managers/permissionManager.ts +116 -168
  53. package/src/managers/pluginManager.ts +29 -0
  54. package/src/managers/subagentManager.ts +6 -0
  55. package/src/prompts/index.ts +144 -37
  56. package/src/services/aiService.ts +9 -128
  57. package/src/services/configurationService.ts +37 -0
  58. package/src/services/remoteSettingsService.ts +1 -0
  59. package/src/services/session.ts +18 -4
  60. package/src/services/taskManager.ts +46 -7
  61. package/src/tools/editTool.ts +29 -11
  62. package/src/tools/enterWorktreeTool.ts +2 -1
  63. package/src/tools/grepTool.ts +11 -2
  64. package/src/tools/writeTool.ts +43 -0
  65. package/src/types/configuration.ts +5 -0
  66. package/src/types/permissions.ts +0 -2
  67. package/src/types/processes.ts +29 -0
  68. package/src/types/workflow.ts +1 -0
  69. package/src/utils/bashParser.ts +106 -0
  70. package/src/utils/configPaths.ts +7 -0
  71. package/src/utils/containerSetup.ts +0 -11
  72. package/src/utils/fileSearch.ts +6 -2
  73. package/src/utils/worktreeSession.ts +1 -1
  74. package/src/utils/worktreeUtils.ts +14 -4
  75. package/src/workflow/types.ts +6 -0
@@ -164,6 +164,13 @@ export declare class PermissionManager {
164
164
  * Check if a tool call matches a specific permission rule
165
165
  */
166
166
  private matchesRule;
167
+ /**
168
+ * Check if a single bash command part is auto-allowed (read-only and safe).
169
+ * Auto-allowed commands skip the confirmation dialog entirely.
170
+ * FR-019.2 through FR-019.7: read-only commands without write redirections,
171
+ * command substitution, process substitution, or sed -i are auto-allowed.
172
+ */
173
+ private isAutoAllowedPart;
167
174
  /**
168
175
  * Check if a tool call is allowed by persistent or temporary rules
169
176
  */
@@ -8,25 +8,9 @@
8
8
  import path from "node:path";
9
9
  import { minimatch } from "minimatch";
10
10
  import { RESTRICTED_TOOLS } from "../types/permissions.js";
11
- import { splitBashCommand, stripEnvVars, stripRedirections, hasWriteRedirections, getSmartPrefix, isDangerousFind, DANGEROUS_COMMANDS, } from "../utils/bashParser.js";
11
+ import { splitBashCommand, stripEnvVars, stripRedirections, hasWriteRedirections, getSmartPrefix, isDangerousFind, hasCommandSubstitution, hasProcessSubstitution, hasSedInPlace, DANGEROUS_COMMANDS, READ_ONLY_COMMANDS, } from "../utils/bashParser.js";
12
12
  import { isPathInside } from "../utils/pathSafety.js";
13
13
  import { BASH_TOOL_NAME, EDIT_TOOL_NAME, WRITE_TOOL_NAME, READ_TOOL_NAME, ASK_USER_QUESTION_TOOL_NAME, } from "../constants/tools.js";
14
- const SAFE_COMMANDS = [
15
- "cd",
16
- "ls",
17
- "pwd",
18
- "true",
19
- "false",
20
- "grep",
21
- "rg",
22
- "cat",
23
- "head",
24
- "tail",
25
- "wc",
26
- "sleep",
27
- "find",
28
- "sort",
29
- ];
30
14
  const DEFAULT_ALLOWED_RULES = [
31
15
  "Bash(git status*)",
32
16
  "Bash(git diff*)",
@@ -573,6 +557,12 @@ export class PermissionManager {
573
557
  if (hasWriteRedirections(part)) {
574
558
  return true;
575
559
  }
560
+ // Command/process substitution and sed -i are dangerous (FR-019.5, FR-019.6)
561
+ if (hasCommandSubstitution(part) ||
562
+ hasProcessSubstitution(part) ||
563
+ hasSedInPlace(part)) {
564
+ return true;
565
+ }
576
566
  const processedPart = stripRedirections(stripEnvVars(part));
577
567
  const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
578
568
  if (commandMatch) {
@@ -665,95 +655,101 @@ export class PermissionManager {
665
655
  return false;
666
656
  }
667
657
  /**
668
- * Check if a tool call is allowed by persistent or temporary rules
658
+ * Check if a single bash command part is auto-allowed (read-only and safe).
659
+ * Auto-allowed commands skip the confirmation dialog entirely.
660
+ * FR-019.2 through FR-019.7: read-only commands without write redirections,
661
+ * command substitution, process substitution, or sed -i are auto-allowed.
669
662
  */
670
- isAllowedByRule(context) {
671
- const isAllowedByRuleList = (ctx, rules, isDefaultRules = false) => {
672
- if (ctx.toolName === BASH_TOOL_NAME && ctx.toolInput?.command) {
673
- const command = String(ctx.toolInput.command);
674
- const parts = splitBashCommand(command);
675
- if (parts.length === 0)
676
- return false;
677
- const workdir = ctx.toolInput?.workdir;
678
- return parts.every((part) => {
679
- const hasWrite = hasWriteRedirections(part);
680
- const processedPart = stripRedirections(stripEnvVars(part));
681
- // Check for safe commands
682
- if (!hasWrite) {
683
- const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
684
- if (commandMatch) {
685
- const cmd = commandMatch[1];
686
- const args = commandMatch[2]?.trim() || "";
687
- if (SAFE_COMMANDS.includes(cmd)) {
688
- if (cmd === "pwd" ||
689
- cmd === "true" ||
690
- cmd === "false" ||
691
- cmd === "ls" ||
692
- cmd === "grep" ||
693
- cmd === "rg" ||
694
- cmd === "cat" ||
695
- cmd === "head" ||
696
- cmd === "tail" ||
697
- cmd === "wc" ||
698
- cmd === "sleep" ||
699
- cmd === "sort" ||
700
- (cmd === "find" && !isDangerousFind(part))) {
701
- return true;
702
- }
703
- if (workdir) {
704
- // For cd, check paths
705
- const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
706
- if (pathArgs.length === 0) {
707
- // cd without arguments operates on current dir (workdir)
708
- return true;
709
- }
710
- const allPathsSafe = pathArgs.every((pathArg) => {
711
- // Remove quotes if present
712
- const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
713
- const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
714
- return isInside;
715
- });
716
- if (allPathsSafe) {
717
- return true;
718
- }
719
- }
720
- }
721
- }
722
- }
723
- // Check if this specific part is allowed by any rule
724
- if (isDefaultRules && (hasWrite || isDangerousFind(part))) {
725
- return false;
726
- }
727
- // We create a temporary context with just this part of the command
728
- const partContext = {
729
- ...ctx,
730
- toolInput: { ...ctx.toolInput, command: part },
731
- };
732
- const allowedByRule = rules.some((rule) => {
733
- return this.matchesRule(partContext, rule);
734
- });
735
- if (allowedByRule)
736
- return true;
737
- return !this.isRestrictedTool(ctx.toolName);
738
- });
739
- }
740
- // For other tools, check if any rule matches
741
- return rules.some((rule) => this.matchesRule(ctx, rule));
742
- };
743
- // Check instance-specific allowed rules first
744
- if (isAllowedByRuleList(context, this.instanceAllowedRules)) {
663
+ isAutoAllowedPart(part, workdir) {
664
+ // Write redirections disqualify (FR-019.4)
665
+ if (hasWriteRedirections(part))
666
+ return false;
667
+ // Command substitution $(...) or `...` disqualifies (FR-019.6)
668
+ if (hasCommandSubstitution(part))
669
+ return false;
670
+ // Process substitution <(...) or >(...) disqualifies (FR-019.6)
671
+ if (hasProcessSubstitution(part))
672
+ return false;
673
+ const processedPart = stripRedirections(stripEnvVars(part));
674
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
675
+ if (!commandMatch)
676
+ return false;
677
+ const cmd = commandMatch[1];
678
+ const args = commandMatch[2]?.trim() || "";
679
+ // sed -i (in-place edit) disqualifies (FR-019.5)
680
+ if (hasSedInPlace(part))
681
+ return false;
682
+ // Read-only commands are auto-allowed (FR-019.2, FR-019.3)
683
+ if (READ_ONLY_COMMANDS.includes(cmd)) {
684
+ // find with dangerous flags (e.g. -exec, -delete) disqualifies
685
+ if (cmd === "find" && isDangerousFind(part))
686
+ return false;
745
687
  return true;
746
688
  }
747
- // Check temporary rules
748
- if (isAllowedByRuleList(context, this.temporaryRules)) {
749
- return true;
689
+ // cd is not read-only but is safe if all paths are within the Safe Zone
690
+ if (cmd === "cd") {
691
+ if (!workdir)
692
+ return false;
693
+ const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
694
+ if (pathArgs.length === 0)
695
+ return true; // cd without args = current dir
696
+ return pathArgs.every((pathArg) => {
697
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
698
+ const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
699
+ return isInside;
700
+ });
750
701
  }
751
- // Check persistent allowed rules
752
- if (isAllowedByRuleList(context, this.allowedRules)) {
753
- return true;
702
+ return false;
703
+ }
704
+ /**
705
+ * Check if a tool call is allowed by persistent or temporary rules
706
+ */
707
+ isAllowedByRule(context) {
708
+ // All allow-rule sources are matched as a union: each part of a chained
709
+ // command only needs to hit at least one rule across all sources, so
710
+ // different parts may be covered by different sources.
711
+ const explicitRules = [
712
+ ...this.instanceAllowedRules,
713
+ ...this.temporaryRules,
714
+ ...this.allowedRules,
715
+ ];
716
+ if (context.toolName === BASH_TOOL_NAME && context.toolInput?.command) {
717
+ const command = String(context.toolInput.command);
718
+ const parts = splitBashCommand(command);
719
+ if (parts.length === 0)
720
+ return false;
721
+ const workdir = context.toolInput?.workdir;
722
+ return parts.every((part) => {
723
+ // Check for auto-allowed read-only commands (FR-019.2 through FR-019.7)
724
+ if (this.isAutoAllowedPart(part, workdir)) {
725
+ return true;
726
+ }
727
+ // We create a temporary context with just this part of the command
728
+ const partContext = {
729
+ ...context,
730
+ toolInput: { ...context.toolInput, command: part },
731
+ };
732
+ if (explicitRules.some((rule) => this.matchesRule(partContext, rule))) {
733
+ return true;
734
+ }
735
+ // Default rules must not auto-allow dangerous variants (write
736
+ // redirections, substitutions, sed -i, dangerous find) through broad
737
+ // rules like Bash(echo*) or Bash(cat*)
738
+ const isDangerousVariant = hasWriteRedirections(part) ||
739
+ isDangerousFind(part) ||
740
+ hasCommandSubstitution(part) ||
741
+ hasProcessSubstitution(part) ||
742
+ hasSedInPlace(part);
743
+ if (!isDangerousVariant &&
744
+ DEFAULT_ALLOWED_RULES.some((rule) => this.matchesRule(partContext, rule))) {
745
+ return true;
746
+ }
747
+ return !this.isRestrictedTool(context.toolName);
748
+ });
754
749
  }
755
- // Check default allowed rules
756
- return isAllowedByRuleList(context, DEFAULT_ALLOWED_RULES, true);
750
+ // For other tools, check if any rule matches
751
+ const allRules = [...explicitRules, ...DEFAULT_ALLOWED_RULES];
752
+ return allRules.some((rule) => this.matchesRule(context, rule));
757
753
  }
758
754
  /**
759
755
  * Expand a bash command into individual permission rules, filtering out safe commands.
@@ -769,53 +765,17 @@ export class PermissionManager {
769
765
  for (const part of parts) {
770
766
  const hasWrite = hasWriteRedirections(part);
771
767
  const processedPart = stripRedirections(stripEnvVars(part));
772
- // Check for safe commands
773
- const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
774
- let isSafe = false;
775
- if (commandMatch && !hasWrite) {
776
- const cmd = commandMatch[1];
777
- const args = commandMatch[2]?.trim() || "";
778
- if (SAFE_COMMANDS.includes(cmd)) {
779
- if (cmd === "pwd" ||
780
- cmd === "true" ||
781
- cmd === "false" ||
782
- cmd === "ls" ||
783
- cmd === "grep" ||
784
- cmd === "rg" ||
785
- cmd === "cat" ||
786
- cmd === "head" ||
787
- cmd === "tail" ||
788
- cmd === "wc" ||
789
- cmd === "sleep" ||
790
- (cmd === "find" && !isDangerousFind(part))) {
791
- isSafe = true;
792
- }
793
- else {
794
- // For cd, check paths
795
- const pathArgs = (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter((arg) => !arg.startsWith("-")) || [];
796
- if (pathArgs.length === 0) {
797
- isSafe = true;
798
- }
799
- else {
800
- const allPathsSafe = pathArgs.every((pathArg) => {
801
- const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
802
- const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
803
- return isInside;
804
- });
805
- if (allPathsSafe) {
806
- isSafe = true;
807
- }
808
- }
809
- }
810
- }
811
- }
768
+ // Check for auto-allowed read-only commands
769
+ const isSafe = this.isAutoAllowedPart(part, workdir);
812
770
  if (!isSafe) {
813
771
  // Check if command is dangerous or out-of-bounds
814
772
  const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
815
773
  if (commandMatch) {
816
774
  const cmd = commandMatch[1];
817
775
  const args = commandMatch[2]?.trim() || "";
818
- if (DANGEROUS_COMMANDS.includes(cmd) || isDangerousFind(part)) {
776
+ if (DANGEROUS_COMMANDS.includes(cmd) ||
777
+ isDangerousFind(part) ||
778
+ hasSedInPlace(part)) {
819
779
  continue;
820
780
  }
821
781
  if (cmd === "cd") {
@@ -34,6 +34,13 @@ export declare class PluginManager {
34
34
  * @param configs Array of plugin configurations
35
35
  */
36
36
  loadPlugins(configs: PluginConfig[]): Promise<void>;
37
+ /**
38
+ * Load built-in plugins bundled with the SDK (e.g. sdd).
39
+ * Opt-in via `enabledPlugins`, keyed `<name>@builtin` (default off, consistent
40
+ * with marketplace plugins). Lowest priority: explicit config and marketplace
41
+ * plugins win on name conflicts (loadSinglePlugin skips duplicates).
42
+ */
43
+ private loadBuiltinPlugins;
37
44
  /**
38
45
  * Get all loaded plugins
39
46
  */
@@ -1,6 +1,8 @@
1
1
  import { logger } from "../utils/globalLogger.js";
2
2
  import { PluginLoader } from "../services/pluginLoader.js";
3
3
  import * as path from "path";
4
+ import { existsSync, readdirSync } from "fs";
5
+ import { getBuiltinPluginsDir } from "../utils/configPaths.js";
4
6
  import { MarketplaceService } from "../services/MarketplaceService.js";
5
7
  export class PluginManager {
6
8
  constructor(container, options) {
@@ -61,6 +63,9 @@ export class PluginManager {
61
63
  const [name, marketplaceName] = pluginId.split("@");
62
64
  if (!name || !marketplaceName)
63
65
  continue;
66
+ // `@builtin` entries are loaded by loadBuiltinPlugins, not the marketplace.
67
+ if (marketplaceName === "builtin")
68
+ continue;
64
69
  const isInstalled = installedRegistry.plugins.some((p) => p.name === name && p.marketplace === marketplaceName);
65
70
  if (!isInstalled) {
66
71
  const isMarketplaceKnown = knownMarketplaces.some((m) => m.name === marketplaceName);
@@ -195,6 +200,32 @@ export class PluginManager {
195
200
  }
196
201
  // Load installed plugins from marketplace
197
202
  await this.loadInstalledPlugins();
203
+ // Load built-in plugins bundled with the SDK (lowest priority)
204
+ await this.loadBuiltinPlugins();
205
+ }
206
+ /**
207
+ * Load built-in plugins bundled with the SDK (e.g. sdd).
208
+ * Opt-in via `enabledPlugins`, keyed `<name>@builtin` (default off, consistent
209
+ * with marketplace plugins). Lowest priority: explicit config and marketplace
210
+ * plugins win on name conflicts (loadSinglePlugin skips duplicates).
211
+ */
212
+ async loadBuiltinPlugins() {
213
+ try {
214
+ const builtinDir = getBuiltinPluginsDir();
215
+ if (!existsSync(builtinDir))
216
+ return;
217
+ // The builtin plugin directory name is the plugin name by convention.
218
+ for (const entry of readdirSync(builtinDir, { withFileTypes: true })) {
219
+ if (!entry.isDirectory())
220
+ continue;
221
+ if (this.enabledPlugins[`${entry.name}@builtin`] !== true)
222
+ continue;
223
+ await this.loadSinglePlugin(path.join(builtinDir, entry.name));
224
+ }
225
+ }
226
+ catch (error) {
227
+ logger?.error("Failed to load built-in plugins:", error);
228
+ }
198
229
  }
199
230
  /**
200
231
  * Get all loaded plugins
@@ -8,6 +8,7 @@ import { ToolManager } from "./toolManager.js";
8
8
  import { AGENT_TOOL_NAME, TASK_CREATE_TOOL_NAME, TASK_GET_TOOL_NAME, TASK_LIST_TOOL_NAME, TASK_UPDATE_TOOL_NAME, } from "../constants/tools.js";
9
9
  import { addConsolidatedAbortListener, createAbortPromise, } from "../utils/abortUtils.js";
10
10
  import { BackgroundTaskManager } from "./backgroundTaskManager.js";
11
+ import { MessageQueue } from "./messageQueue.js";
11
12
  import { logger } from "../utils/globalLogger.js";
12
13
  export class SubagentManager {
13
14
  constructor(container, options) {
@@ -135,6 +136,11 @@ export class SubagentManager {
135
136
  const subagentId = randomUUID();
136
137
  // Create a child container for the subagent to isolate its managers
137
138
  const subagentContainer = this.container.createChild();
139
+ // Register an independent MessageQueue so the subagent's AIManager drains its
140
+ // own (empty) queue instead of falling back to the parent container's queue.
141
+ // Without this, concurrent background subagents steal sibling completion
142
+ // notifications from the parent queue, causing the main agent to exit early.
143
+ subagentContainer.register("MessageQueue", new MessageQueue());
138
144
  // Register a modified AgentOptions without onLoadingChange to prevent subagent loading
139
145
  // from affecting the parent agent's loading state
140
146
  const parentOptions = this.container.get("AgentOptions");
@@ -21,7 +21,18 @@ export interface SystemPromptBlock {
21
21
  text: string;
22
22
  cacheable: boolean;
23
23
  }
24
- export declare const COMPACT_MESSAGES_SYSTEM_PROMPT = "You are continuing work on a software engineering task. Write a detailed continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary.\n\nFirst, write your analysis in <analysis> tags as a thinking scratchpad:\n- Chronologically review the conversation\n- Identify user intents and goals\n- Note files read/modified, approaches tried, decisions made\n- Check for accuracy and completeness \u2014 ensure nothing critical is missing\n\nThen produce a structured summary in <summary> tags with these sections:\n\n## Primary Request and Intent\n- The user's core request and success criteria\n- Clarifications, constraints, or scope changes\n\n## Key Technical Concepts\n- Frameworks, libraries, patterns, architectural decisions\n\n## Files and Code Sections\n- Files read, modified, created (with full paths)\n- Critical code snippets (function signatures, bug fixes, key logic)\n- Focus on recent messages \u2014 include full code for important sections\n\n## Errors and Fixes\n- Errors encountered, root causes, how they were resolved\n- Approaches tried that didn't work and why\n\n## Problem Solving\n- Approach evolution, trade-offs considered, decisions made\n\n## All User Messages\n- Complete list of all user messages (non-tool content)\n- Preserve exact wording where load-bearing\n\n## Pending Tasks\n- Outstanding work, TODOs, unresolved questions\n\n## Current Work\n- What was being worked on at the time of summarization\n- Exact state of in-progress changes\n\n## Optional Next Step\n- Immediate next action needed\n- Include verbatim quotes from recent conversation if relevant\n\nBe concise but complete \u2014 include information that prevents duplicate work or repeated mistakes.\nRespond with text only. Do NOT call any tools.\nWrap your summary in <summary></summary> tags.";
24
+ /**
25
+ * Builds the compact instruction sent as the trailing user message on the
26
+ * fork path. Aligned with Claude Code's getCompactPrompt().
27
+ */
28
+ export declare function getCompactPrompt(customInstructions?: string): string;
29
+ /**
30
+ * Formats the compact summary by stripping the <analysis> drafting scratchpad
31
+ * and extracting the <summary> section. Raw text passes through unchanged
32
+ * when no <summary> tag is present. Aligned with Claude Code's
33
+ * formatCompactSummary().
34
+ */
35
+ export declare function formatCompactSummary(summary: string): string;
25
36
  export declare const WEB_CONTENT_SYSTEM_PROMPT = "You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.";
26
37
  export declare const BTW_SYSTEM_PROMPT = "You are a helpful assistant. Answer the user's side question based on the conversation history. \nDo NOT say things like \"Let me try...\", \"I'll now...\", \"Let me check...\", or promise to take any action. \nIf you don't know the answer, say so - do not offer to look it up or investigate. \nSimply answer the question with the information you have.";
27
38
  export declare function buildSystemPrompt(basePrompt: string | undefined, tools: ToolPlugin[], options?: {
@@ -149,53 +149,141 @@ This is critical - your turn should only end with either using the ${ASK_USER_QU
149
149
  NOTE: At any point in time through this workflow you should feel free to ask the user questions or clarifications using the ${ASK_USER_QUESTION_TOOL_NAME} tool. Don't make large assumptions about user intent. The goal is to present a well researched plan to the user, and tie any loose ends before implementation begins.`;
150
150
  }
151
151
  export const DEFAULT_SYSTEM_PROMPT = BASE_SYSTEM_PROMPT;
152
- export const COMPACT_MESSAGES_SYSTEM_PROMPT = `You are continuing work on a software engineering task. Write a detailed continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary.
152
+ // Aggressive no-tools preamble, aligned with Claude Code's NO_TOOLS_PREAMBLE.
153
+ // The fork path inherits the main conversation's full tool set (required for
154
+ // cache-key match), so the instruction must be explicit about rejection
155
+ // consequences to prevent wasted turns.
156
+ const COMPACT_NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
153
157
 
154
- First, write your analysis in <analysis> tags as a thinking scratchpad:
155
- - Chronologically review the conversation
156
- - Identify user intents and goals
157
- - Note files read/modified, approaches tried, decisions made
158
- - Check for accuracy and completeness — ensure nothing critical is missing
158
+ - Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
159
+ - You already have all the context you need in the conversation above.
160
+ - Tool calls will be REJECTED and will waste your only turn — you will fail the task.
161
+ - Your entire response must be plain text: an <analysis> block followed by a <summary> block.
159
162
 
160
- Then produce a structured summary in <summary> tags with these sections:
161
-
162
- ## Primary Request and Intent
163
- - The user's core request and success criteria
164
- - Clarifications, constraints, or scope changes
165
-
166
- ## Key Technical Concepts
167
- - Frameworks, libraries, patterns, architectural decisions
168
-
169
- ## Files and Code Sections
170
- - Files read, modified, created (with full paths)
171
- - Critical code snippets (function signatures, bug fixes, key logic)
172
- - Focus on recent messages — include full code for important sections
173
-
174
- ## Errors and Fixes
175
- - Errors encountered, root causes, how they were resolved
176
- - Approaches tried that didn't work and why
177
-
178
- ## Problem Solving
179
- - Approach evolution, trade-offs considered, decisions made
180
-
181
- ## All User Messages
182
- - Complete list of all user messages (non-tool content)
183
- - Preserve exact wording where load-bearing
184
-
185
- ## Pending Tasks
186
- - Outstanding work, TODOs, unresolved questions
187
-
188
- ## Current Work
189
- - What was being worked on at the time of summarization
190
- - Exact state of in-progress changes
191
-
192
- ## Optional Next Step
193
- - Immediate next action needed
194
- - Include verbatim quotes from recent conversation if relevant
195
-
196
- Be concise but complete — include information that prevents duplicate work or repeated mistakes.
197
- Respond with text only. Do NOT call any tools.
198
- Wrap your summary in <summary></summary> tags.`;
163
+ `;
164
+ // Aligned with Claude Code's DETAILED_ANALYSIS_INSTRUCTION_BASE. The
165
+ // <analysis> block is a drafting scratchpad that formatCompactSummary()
166
+ // strips before the summary reaches context.
167
+ const COMPACT_DETAILED_ANALYSIS_INSTRUCTION = `Before providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:
168
+
169
+ 1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
170
+ - The user's explicit requests and intents
171
+ - Your approach to addressing the user's requests
172
+ - Key decisions, technical concepts and code patterns
173
+ - Specific details like:
174
+ - file names
175
+ - full code snippets
176
+ - function signatures
177
+ - file edits
178
+ - Errors that you ran into and how you fixed them
179
+ - Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
180
+ 2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.`;
181
+ // Aligned with Claude Code's BASE_COMPACT_PROMPT (9 sections + example).
182
+ const BASE_COMPACT_PROMPT = `Your task is to create a detailed summary of the conversation so far, paying close attention to the user's explicit requests and your previous actions.
183
+ This summary should be thorough in capturing technical details, code patterns, and architectural decisions that would be essential for continuing development work without losing context.
184
+
185
+ ${COMPACT_DETAILED_ANALYSIS_INSTRUCTION}
186
+
187
+ Your summary should include the following sections:
188
+
189
+ 1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
190
+ 2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
191
+ 3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Pay special attention to the most recent messages and include full code snippets where applicable and include a summary of why this file read or edit is important.
192
+ 4. Errors and fixes: List all errors that you ran into, and how you fixed them. Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
193
+ 5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
194
+ 6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent.
195
+ 7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
196
+ 8. Current Work: Describe in detail precisely what was being worked on immediately before this summary request, paying special attention to the most recent messages from both user and assistant. Include file names and code snippets where applicable.
197
+ 9. Optional Next Step: List the next step that you will take that is related to the most recent work you were doing. IMPORTANT: ensure that this step is DIRECTLY in line with the user's most recent explicit requests, and the task you were working on immediately before this summary request. If your last task was concluded, then only list next steps if they are explicitly in line with the users request. Do not start on tangential requests or really old requests that were already completed without confirming with the user first.
198
+ If there is a next step, include direct quotes from the most recent conversation showing exactly what task you were working on and where you left off. This should be verbatim to ensure there's no drift in task interpretation.
199
+
200
+ Here's an example of how your output should be structured:
201
+
202
+ <example>
203
+ <analysis>
204
+ [Your thought process, ensuring all points are covered thoroughly and accurately]
205
+ </analysis>
206
+
207
+ <summary>
208
+ 1. Primary Request and Intent:
209
+ [Detailed description]
210
+
211
+ 2. Key Technical Concepts:
212
+ - [Concept 1]
213
+ - [Concept 2]
214
+ - [...]
215
+
216
+ 3. Files and Code Sections:
217
+ - [File Name 1]
218
+ - [Summary of why this file is important]
219
+ - [Summary of the changes made to this file, if any]
220
+ - [Important Code Snippet]
221
+ - [File Name 2]
222
+ - [Important Code Snippet]
223
+ - [...]
224
+
225
+ 4. Errors and fixes:
226
+ - [Detailed description of error 1]:
227
+ - [How you fixed the error]
228
+ - [User feedback on the error if any]
229
+ - [...]
230
+
231
+ 5. Problem Solving:
232
+ [Description of solved problems and ongoing troubleshooting]
233
+
234
+ 6. All user messages:
235
+ - [Detailed non tool use user message]
236
+ - [...]
237
+
238
+ 7. Pending Tasks:
239
+ - [Task 1]
240
+ - [Task 2]
241
+ - [...]
242
+
243
+ 8. Current Work:
244
+ [Precise description of current work]
245
+
246
+ 9. Optional Next Step:
247
+ [Optional Next step to take]
248
+
249
+ </summary>
250
+ </example>
251
+
252
+ Please provide your summary based on the conversation so far, following this structure and ensuring precision and thoroughness in your response.
253
+
254
+ There may be additional summarization instructions provided in the included context. If so, remember to follow these instructions when creating the above summary.`;
255
+ const COMPACT_NO_TOOLS_TRAILER = "\n\nREMINDER: Do NOT call any tools. Respond with plain text only — " +
256
+ "an <analysis> block followed by a <summary> block. " +
257
+ "Tool calls will be rejected and you will fail the task.";
258
+ /**
259
+ * Builds the compact instruction sent as the trailing user message on the
260
+ * fork path. Aligned with Claude Code's getCompactPrompt().
261
+ */
262
+ export function getCompactPrompt(customInstructions) {
263
+ let prompt = COMPACT_NO_TOOLS_PREAMBLE + BASE_COMPACT_PROMPT;
264
+ if (customInstructions && customInstructions.trim() !== "") {
265
+ prompt += `\n\nAdditional Instructions:\n${customInstructions}`;
266
+ }
267
+ prompt += COMPACT_NO_TOOLS_TRAILER;
268
+ return prompt;
269
+ }
270
+ /**
271
+ * Formats the compact summary by stripping the <analysis> drafting scratchpad
272
+ * and extracting the <summary> section. Raw text passes through unchanged
273
+ * when no <summary> tag is present. Aligned with Claude Code's
274
+ * formatCompactSummary().
275
+ */
276
+ export function formatCompactSummary(summary) {
277
+ let formattedSummary = summary;
278
+ formattedSummary = formattedSummary.replace(/<analysis>[\s\S]*?<\/analysis>/, "");
279
+ const summaryMatch = formattedSummary.match(/<summary>([\s\S]*?)<\/summary>/);
280
+ if (summaryMatch) {
281
+ const content = summaryMatch[1] || "";
282
+ formattedSummary = formattedSummary.replace(/<summary>[\s\S]*?<\/summary>/, `Summary:\n${content.trim()}`);
283
+ }
284
+ formattedSummary = formattedSummary.replace(/\n\n+/g, "\n\n");
285
+ return formattedSummary.trim();
286
+ }
199
287
  export const WEB_CONTENT_SYSTEM_PROMPT = `You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.`;
200
288
  export const BTW_SYSTEM_PROMPT = `You are a helpful assistant. Answer the user's side question based on the conversation history.
201
289
  Do NOT say things like "Let me try...", "I'll now...", "Let me check...", or promise to take any action.
@@ -24,6 +24,7 @@ export interface CallAgentOptions {
24
24
  name: string;
25
25
  };
26
26
  };
27
+ stream?: boolean;
27
28
  onContentUpdate?: (content: string) => void;
28
29
  onToolUpdate?: (toolCall: {
29
30
  id: string;
@@ -44,23 +45,6 @@ export interface CallAgentResult {
44
45
  additionalFields?: Record<string, unknown>;
45
46
  }
46
47
  export declare function callAgent(options: CallAgentOptions): Promise<CallAgentResult>;
47
- export interface CompactMessagesOptions {
48
- gatewayConfig: GatewayConfig;
49
- modelConfig: ModelConfig;
50
- messages: ChatCompletionMessageParam[];
51
- abortSignal?: AbortSignal;
52
- model?: string;
53
- customInstructions?: string;
54
- }
55
- export interface CompactMessagesResult {
56
- content: string;
57
- usage?: {
58
- prompt_tokens: number;
59
- completion_tokens: number;
60
- total_tokens: number;
61
- };
62
- }
63
- export declare function compactMessages(options: CompactMessagesOptions): Promise<CompactMessagesResult>;
64
48
  export interface ProcessWebContentOptions {
65
49
  gatewayConfig: GatewayConfig;
66
50
  modelConfig: ModelConfig;