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
@@ -23,7 +23,11 @@ import {
23
23
  hasWriteRedirections,
24
24
  getSmartPrefix,
25
25
  isDangerousFind,
26
+ hasCommandSubstitution,
27
+ hasProcessSubstitution,
28
+ hasSedInPlace,
26
29
  DANGEROUS_COMMANDS,
30
+ READ_ONLY_COMMANDS,
27
31
  } from "../utils/bashParser.js";
28
32
  import { isPathInside } from "../utils/pathSafety.js";
29
33
  import {
@@ -37,23 +41,6 @@ import { Container } from "../utils/container.js";
37
41
  import { ConfigurationService } from "../services/configurationService.js";
38
42
  import type { WorktreeSession } from "../utils/worktreeSession.js";
39
43
 
40
- const SAFE_COMMANDS = [
41
- "cd",
42
- "ls",
43
- "pwd",
44
- "true",
45
- "false",
46
- "grep",
47
- "rg",
48
- "cat",
49
- "head",
50
- "tail",
51
- "wc",
52
- "sleep",
53
- "find",
54
- "sort",
55
- ];
56
-
57
44
  const DEFAULT_ALLOWED_RULES = [
58
45
  "Bash(git status*)",
59
46
  "Bash(git diff*)",
@@ -747,6 +734,14 @@ export class PermissionManager {
747
734
  if (hasWriteRedirections(part)) {
748
735
  return true;
749
736
  }
737
+ // Command/process substitution and sed -i are dangerous (FR-019.5, FR-019.6)
738
+ if (
739
+ hasCommandSubstitution(part) ||
740
+ hasProcessSubstitution(part) ||
741
+ hasSedInPlace(part)
742
+ ) {
743
+ return true;
744
+ }
750
745
  const processedPart = stripRedirections(stripEnvVars(part));
751
746
  const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
752
747
  if (commandMatch) {
@@ -862,122 +857,115 @@ export class PermissionManager {
862
857
  }
863
858
 
864
859
  /**
865
- * Check if a tool call is allowed by persistent or temporary rules
860
+ * Check if a single bash command part is auto-allowed (read-only and safe).
861
+ * Auto-allowed commands skip the confirmation dialog entirely.
862
+ * FR-019.2 through FR-019.7: read-only commands without write redirections,
863
+ * command substitution, process substitution, or sed -i are auto-allowed.
866
864
  */
867
- private isAllowedByRule(context: ToolPermissionContext): boolean {
868
- const isAllowedByRuleList = (
869
- ctx: ToolPermissionContext,
870
- rules: string[],
871
- isDefaultRules: boolean = false,
872
- ) => {
873
- if (ctx.toolName === BASH_TOOL_NAME && ctx.toolInput?.command) {
874
- const command = String(ctx.toolInput.command);
875
- const parts = splitBashCommand(command);
876
- if (parts.length === 0) return false;
877
-
878
- const workdir = ctx.toolInput?.workdir as string | undefined;
879
-
880
- return parts.every((part) => {
881
- const hasWrite = hasWriteRedirections(part);
882
- const processedPart = stripRedirections(stripEnvVars(part));
883
-
884
- // Check for safe commands
885
- if (!hasWrite) {
886
- const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
887
- if (commandMatch) {
888
- const cmd = commandMatch[1];
889
- const args = commandMatch[2]?.trim() || "";
890
-
891
- if (SAFE_COMMANDS.includes(cmd)) {
892
- if (
893
- cmd === "pwd" ||
894
- cmd === "true" ||
895
- cmd === "false" ||
896
- cmd === "ls" ||
897
- cmd === "grep" ||
898
- cmd === "rg" ||
899
- cmd === "cat" ||
900
- cmd === "head" ||
901
- cmd === "tail" ||
902
- cmd === "wc" ||
903
- cmd === "sleep" ||
904
- cmd === "sort" ||
905
- (cmd === "find" && !isDangerousFind(part))
906
- ) {
907
- return true;
908
- }
865
+ private isAutoAllowedPart(part: string, workdir?: string): boolean {
866
+ // Write redirections disqualify (FR-019.4)
867
+ if (hasWriteRedirections(part)) return false;
868
+ // Command substitution $(...) or `...` disqualifies (FR-019.6)
869
+ if (hasCommandSubstitution(part)) return false;
870
+ // Process substitution <(...) or >(...) disqualifies (FR-019.6)
871
+ if (hasProcessSubstitution(part)) return false;
872
+
873
+ const processedPart = stripRedirections(stripEnvVars(part));
874
+ const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
875
+ if (!commandMatch) return false;
876
+
877
+ const cmd = commandMatch[1];
878
+ const args = commandMatch[2]?.trim() || "";
879
+
880
+ // sed -i (in-place edit) disqualifies (FR-019.5)
881
+ if (hasSedInPlace(part)) return false;
882
+
883
+ // Read-only commands are auto-allowed (FR-019.2, FR-019.3)
884
+ if (READ_ONLY_COMMANDS.includes(cmd)) {
885
+ // find with dangerous flags (e.g. -exec, -delete) disqualifies
886
+ if (cmd === "find" && isDangerousFind(part)) return false;
887
+ return true;
888
+ }
909
889
 
910
- if (workdir) {
911
- // For cd, check paths
912
- const pathArgs =
913
- (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
914
- (arg) => !arg.startsWith("-"),
915
- ) || [];
916
-
917
- if (pathArgs.length === 0) {
918
- // cd without arguments operates on current dir (workdir)
919
- return true;
920
- }
921
-
922
- const allPathsSafe = pathArgs.every((pathArg) => {
923
- // Remove quotes if present
924
- const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
925
- const { isInside } = this.isInsideSafeZone(
926
- cleanPath,
927
- workdir,
928
- );
929
- return isInside;
930
- });
931
-
932
- if (allPathsSafe) {
933
- return true;
934
- }
935
- }
936
- }
937
- }
938
- }
890
+ // cd is not read-only but is safe if all paths are within the Safe Zone
891
+ if (cmd === "cd") {
892
+ if (!workdir) return false;
893
+ const pathArgs =
894
+ (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
895
+ (arg) => !arg.startsWith("-"),
896
+ ) || [];
897
+ if (pathArgs.length === 0) return true; // cd without args = current dir
898
+ return pathArgs.every((pathArg) => {
899
+ const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
900
+ const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
901
+ return isInside;
902
+ });
903
+ }
939
904
 
940
- // Check if this specific part is allowed by any rule
941
- if (isDefaultRules && (hasWrite || isDangerousFind(part))) {
942
- return false;
943
- }
905
+ return false;
906
+ }
944
907
 
945
- // We create a temporary context with just this part of the command
946
- const partContext = {
947
- ...ctx,
948
- toolInput: { ...ctx.toolInput, command: part },
949
- };
950
- const allowedByRule = rules.some((rule) => {
951
- return this.matchesRule(partContext, rule);
952
- });
908
+ /**
909
+ * Check if a tool call is allowed by persistent or temporary rules
910
+ */
911
+ private isAllowedByRule(context: ToolPermissionContext): boolean {
912
+ // All allow-rule sources are matched as a union: each part of a chained
913
+ // command only needs to hit at least one rule across all sources, so
914
+ // different parts may be covered by different sources.
915
+ const explicitRules = [
916
+ ...this.instanceAllowedRules,
917
+ ...this.temporaryRules,
918
+ ...this.allowedRules,
919
+ ];
920
+
921
+ if (context.toolName === BASH_TOOL_NAME && context.toolInput?.command) {
922
+ const command = String(context.toolInput.command);
923
+ const parts = splitBashCommand(command);
924
+ if (parts.length === 0) return false;
953
925
 
954
- if (allowedByRule) return true;
926
+ const workdir = context.toolInput?.workdir as string | undefined;
955
927
 
956
- return !this.isRestrictedTool(ctx.toolName);
957
- });
958
- }
928
+ return parts.every((part) => {
929
+ // Check for auto-allowed read-only commands (FR-019.2 through FR-019.7)
930
+ if (this.isAutoAllowedPart(part, workdir)) {
931
+ return true;
932
+ }
959
933
 
960
- // For other tools, check if any rule matches
961
- return rules.some((rule) => this.matchesRule(ctx, rule));
962
- };
934
+ // We create a temporary context with just this part of the command
935
+ const partContext = {
936
+ ...context,
937
+ toolInput: { ...context.toolInput, command: part },
938
+ };
963
939
 
964
- // Check instance-specific allowed rules first
965
- if (isAllowedByRuleList(context, this.instanceAllowedRules)) {
966
- return true;
967
- }
940
+ if (explicitRules.some((rule) => this.matchesRule(partContext, rule))) {
941
+ return true;
942
+ }
968
943
 
969
- // Check temporary rules
970
- if (isAllowedByRuleList(context, this.temporaryRules)) {
971
- return true;
972
- }
944
+ // Default rules must not auto-allow dangerous variants (write
945
+ // redirections, substitutions, sed -i, dangerous find) through broad
946
+ // rules like Bash(echo*) or Bash(cat*)
947
+ const isDangerousVariant =
948
+ hasWriteRedirections(part) ||
949
+ isDangerousFind(part) ||
950
+ hasCommandSubstitution(part) ||
951
+ hasProcessSubstitution(part) ||
952
+ hasSedInPlace(part);
953
+ if (
954
+ !isDangerousVariant &&
955
+ DEFAULT_ALLOWED_RULES.some((rule) =>
956
+ this.matchesRule(partContext, rule),
957
+ )
958
+ ) {
959
+ return true;
960
+ }
973
961
 
974
- // Check persistent allowed rules
975
- if (isAllowedByRuleList(context, this.allowedRules)) {
976
- return true;
962
+ return !this.isRestrictedTool(context.toolName);
963
+ });
977
964
  }
978
965
 
979
- // Check default allowed rules
980
- return isAllowedByRuleList(context, DEFAULT_ALLOWED_RULES, true);
966
+ // For other tools, check if any rule matches
967
+ const allRules = [...explicitRules, ...DEFAULT_ALLOWED_RULES];
968
+ return allRules.some((rule) => this.matchesRule(context, rule));
981
969
  }
982
970
 
983
971
  /**
@@ -996,52 +984,8 @@ export class PermissionManager {
996
984
  const hasWrite = hasWriteRedirections(part);
997
985
  const processedPart = stripRedirections(stripEnvVars(part));
998
986
 
999
- // Check for safe commands
1000
- const commandMatch = processedPart.match(/^(\w+)(\s+.*)?$/);
1001
- let isSafe = false;
1002
-
1003
- if (commandMatch && !hasWrite) {
1004
- const cmd = commandMatch[1];
1005
- const args = commandMatch[2]?.trim() || "";
1006
-
1007
- if (SAFE_COMMANDS.includes(cmd)) {
1008
- if (
1009
- cmd === "pwd" ||
1010
- cmd === "true" ||
1011
- cmd === "false" ||
1012
- cmd === "ls" ||
1013
- cmd === "grep" ||
1014
- cmd === "rg" ||
1015
- cmd === "cat" ||
1016
- cmd === "head" ||
1017
- cmd === "tail" ||
1018
- cmd === "wc" ||
1019
- cmd === "sleep" ||
1020
- (cmd === "find" && !isDangerousFind(part))
1021
- ) {
1022
- isSafe = true;
1023
- } else {
1024
- // For cd, check paths
1025
- const pathArgs =
1026
- (args.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || []).filter(
1027
- (arg) => !arg.startsWith("-"),
1028
- ) || [];
1029
-
1030
- if (pathArgs.length === 0) {
1031
- isSafe = true;
1032
- } else {
1033
- const allPathsSafe = pathArgs.every((pathArg) => {
1034
- const cleanPath = pathArg.replace(/^['"](.*)['"]$/, "$1");
1035
- const { isInside } = this.isInsideSafeZone(cleanPath, workdir);
1036
- return isInside;
1037
- });
1038
- if (allPathsSafe) {
1039
- isSafe = true;
1040
- }
1041
- }
1042
- }
1043
- }
1044
- }
987
+ // Check for auto-allowed read-only commands
988
+ const isSafe = this.isAutoAllowedPart(part, workdir);
1045
989
 
1046
990
  if (!isSafe) {
1047
991
  // Check if command is dangerous or out-of-bounds
@@ -1050,7 +994,11 @@ export class PermissionManager {
1050
994
  const cmd = commandMatch[1];
1051
995
  const args = commandMatch[2]?.trim() || "";
1052
996
 
1053
- if (DANGEROUS_COMMANDS.includes(cmd) || isDangerousFind(part)) {
997
+ if (
998
+ DANGEROUS_COMMANDS.includes(cmd) ||
999
+ isDangerousFind(part) ||
1000
+ hasSedInPlace(part)
1001
+ ) {
1054
1002
  continue;
1055
1003
  }
1056
1004
 
@@ -2,6 +2,8 @@ import { logger } from "../utils/globalLogger.js";
2
2
  import { Plugin, PluginConfig } from "../types/index.js";
3
3
  import { PluginLoader } from "../services/pluginLoader.js";
4
4
  import * as path from "path";
5
+ import { existsSync, readdirSync } from "fs";
6
+ import { getBuiltinPluginsDir } from "../utils/configPaths.js";
5
7
  import { SkillManager } from "./skillManager.js";
6
8
  import { HookManager } from "./hookManager.js";
7
9
  import { LspManager } from "./lspManager.js";
@@ -98,6 +100,8 @@ export class PluginManager {
98
100
 
99
101
  const [name, marketplaceName] = pluginId.split("@");
100
102
  if (!name || !marketplaceName) continue;
103
+ // `@builtin` entries are loaded by loadBuiltinPlugins, not the marketplace.
104
+ if (marketplaceName === "builtin") continue;
101
105
 
102
106
  const isInstalled = installedRegistry.plugins.some(
103
107
  (p) => p.name === name && p.marketplace === marketplaceName,
@@ -283,6 +287,31 @@ export class PluginManager {
283
287
 
284
288
  // Load installed plugins from marketplace
285
289
  await this.loadInstalledPlugins();
290
+
291
+ // Load built-in plugins bundled with the SDK (lowest priority)
292
+ await this.loadBuiltinPlugins();
293
+ }
294
+
295
+ /**
296
+ * Load built-in plugins bundled with the SDK (e.g. sdd).
297
+ * Opt-in via `enabledPlugins`, keyed `<name>@builtin` (default off, consistent
298
+ * with marketplace plugins). Lowest priority: explicit config and marketplace
299
+ * plugins win on name conflicts (loadSinglePlugin skips duplicates).
300
+ */
301
+ private async loadBuiltinPlugins(): Promise<void> {
302
+ try {
303
+ const builtinDir = getBuiltinPluginsDir();
304
+ if (!existsSync(builtinDir)) return;
305
+
306
+ // The builtin plugin directory name is the plugin name by convention.
307
+ for (const entry of readdirSync(builtinDir, { withFileTypes: true })) {
308
+ if (!entry.isDirectory()) continue;
309
+ if (this.enabledPlugins[`${entry.name}@builtin`] !== true) continue;
310
+ await this.loadSinglePlugin(path.join(builtinDir, entry.name));
311
+ }
312
+ } catch (error) {
313
+ logger?.error("Failed to load built-in plugins:", error);
314
+ }
286
315
  }
287
316
 
288
317
  /**
@@ -290,6 +290,12 @@ export class SubagentManager {
290
290
  // Create a child container for the subagent to isolate its managers
291
291
  const subagentContainer = this.container.createChild();
292
292
 
293
+ // Register an independent MessageQueue so the subagent's AIManager drains its
294
+ // own (empty) queue instead of falling back to the parent container's queue.
295
+ // Without this, concurrent background subagents steal sibling completion
296
+ // notifications from the parent queue, causing the main agent to exit early.
297
+ subagentContainer.register("MessageQueue", new MessageQueue());
298
+
293
299
  // Register a modified AgentOptions without onLoadingChange to prevent subagent loading
294
300
  // from affecting the parent agent's loading state
295
301
  const parentOptions =
@@ -190,53 +190,160 @@ export interface SystemPromptBlock {
190
190
  cacheable: boolean;
191
191
  }
192
192
 
193
- 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.
193
+ // Aggressive no-tools preamble, aligned with Claude Code's NO_TOOLS_PREAMBLE.
194
+ // The fork path inherits the main conversation's full tool set (required for
195
+ // cache-key match), so the instruction must be explicit about rejection
196
+ // consequences to prevent wasted turns.
197
+ const COMPACT_NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
194
198
 
195
- First, write your analysis in <analysis> tags as a thinking scratchpad:
196
- - Chronologically review the conversation
197
- - Identify user intents and goals
198
- - Note files read/modified, approaches tried, decisions made
199
- - Check for accuracy and completeness — ensure nothing critical is missing
199
+ - Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
200
+ - You already have all the context you need in the conversation above.
201
+ - Tool calls will be REJECTED and will waste your only turn — you will fail the task.
202
+ - Your entire response must be plain text: an <analysis> block followed by a <summary> block.
200
203
 
201
- Then produce a structured summary in <summary> tags with these sections:
202
-
203
- ## Primary Request and Intent
204
- - The user's core request and success criteria
205
- - Clarifications, constraints, or scope changes
206
-
207
- ## Key Technical Concepts
208
- - Frameworks, libraries, patterns, architectural decisions
204
+ `;
209
205
 
210
- ## Files and Code Sections
211
- - Files read, modified, created (with full paths)
212
- - Critical code snippets (function signatures, bug fixes, key logic)
213
- - Focus on recent messages include full code for important sections
206
+ // Aligned with Claude Code's DETAILED_ANALYSIS_INSTRUCTION_BASE. The
207
+ // <analysis> block is a drafting scratchpad that formatCompactSummary()
208
+ // strips before the summary reaches context.
209
+ 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:
210
+
211
+ 1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
212
+ - The user's explicit requests and intents
213
+ - Your approach to addressing the user's requests
214
+ - Key decisions, technical concepts and code patterns
215
+ - Specific details like:
216
+ - file names
217
+ - full code snippets
218
+ - function signatures
219
+ - file edits
220
+ - Errors that you ran into and how you fixed them
221
+ - Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
222
+ 2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.`;
223
+
224
+ // Aligned with Claude Code's BASE_COMPACT_PROMPT (9 sections + example).
225
+ 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.
226
+ 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.
227
+
228
+ ${COMPACT_DETAILED_ANALYSIS_INSTRUCTION}
229
+
230
+ Your summary should include the following sections:
231
+
232
+ 1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
233
+ 2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
234
+ 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.
235
+ 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.
236
+ 5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
237
+ 6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent.
238
+ 7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
239
+ 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.
240
+ 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.
241
+ 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.
242
+
243
+ Here's an example of how your output should be structured:
244
+
245
+ <example>
246
+ <analysis>
247
+ [Your thought process, ensuring all points are covered thoroughly and accurately]
248
+ </analysis>
249
+
250
+ <summary>
251
+ 1. Primary Request and Intent:
252
+ [Detailed description]
253
+
254
+ 2. Key Technical Concepts:
255
+ - [Concept 1]
256
+ - [Concept 2]
257
+ - [...]
258
+
259
+ 3. Files and Code Sections:
260
+ - [File Name 1]
261
+ - [Summary of why this file is important]
262
+ - [Summary of the changes made to this file, if any]
263
+ - [Important Code Snippet]
264
+ - [File Name 2]
265
+ - [Important Code Snippet]
266
+ - [...]
267
+
268
+ 4. Errors and fixes:
269
+ - [Detailed description of error 1]:
270
+ - [How you fixed the error]
271
+ - [User feedback on the error if any]
272
+ - [...]
273
+
274
+ 5. Problem Solving:
275
+ [Description of solved problems and ongoing troubleshooting]
276
+
277
+ 6. All user messages:
278
+ - [Detailed non tool use user message]
279
+ - [...]
280
+
281
+ 7. Pending Tasks:
282
+ - [Task 1]
283
+ - [Task 2]
284
+ - [...]
285
+
286
+ 8. Current Work:
287
+ [Precise description of current work]
288
+
289
+ 9. Optional Next Step:
290
+ [Optional Next step to take]
291
+
292
+ </summary>
293
+ </example>
294
+
295
+ Please provide your summary based on the conversation so far, following this structure and ensuring precision and thoroughness in your response.
296
+
297
+ There may be additional summarization instructions provided in the included context. If so, remember to follow these instructions when creating the above summary.`;
298
+
299
+ const COMPACT_NO_TOOLS_TRAILER =
300
+ "\n\nREMINDER: Do NOT call any tools. Respond with plain text only — " +
301
+ "an <analysis> block followed by a <summary> block. " +
302
+ "Tool calls will be rejected and you will fail the task.";
214
303
 
215
- ## Errors and Fixes
216
- - Errors encountered, root causes, how they were resolved
217
- - Approaches tried that didn't work and why
304
+ /**
305
+ * Builds the compact instruction sent as the trailing user message on the
306
+ * fork path. Aligned with Claude Code's getCompactPrompt().
307
+ */
308
+ export function getCompactPrompt(customInstructions?: string): string {
309
+ let prompt = COMPACT_NO_TOOLS_PREAMBLE + BASE_COMPACT_PROMPT;
218
310
 
219
- ## Problem Solving
220
- - Approach evolution, trade-offs considered, decisions made
311
+ if (customInstructions && customInstructions.trim() !== "") {
312
+ prompt += `\n\nAdditional Instructions:\n${customInstructions}`;
313
+ }
221
314
 
222
- ## All User Messages
223
- - Complete list of all user messages (non-tool content)
224
- - Preserve exact wording where load-bearing
315
+ prompt += COMPACT_NO_TOOLS_TRAILER;
225
316
 
226
- ## Pending Tasks
227
- - Outstanding work, TODOs, unresolved questions
317
+ return prompt;
318
+ }
228
319
 
229
- ## Current Work
230
- - What was being worked on at the time of summarization
231
- - Exact state of in-progress changes
320
+ /**
321
+ * Formats the compact summary by stripping the <analysis> drafting scratchpad
322
+ * and extracting the <summary> section. Raw text passes through unchanged
323
+ * when no <summary> tag is present. Aligned with Claude Code's
324
+ * formatCompactSummary().
325
+ */
326
+ export function formatCompactSummary(summary: string): string {
327
+ let formattedSummary = summary;
328
+
329
+ formattedSummary = formattedSummary.replace(
330
+ /<analysis>[\s\S]*?<\/analysis>/,
331
+ "",
332
+ );
333
+
334
+ const summaryMatch = formattedSummary.match(/<summary>([\s\S]*?)<\/summary>/);
335
+ if (summaryMatch) {
336
+ const content = summaryMatch[1] || "";
337
+ formattedSummary = formattedSummary.replace(
338
+ /<summary>[\s\S]*?<\/summary>/,
339
+ `Summary:\n${content.trim()}`,
340
+ );
341
+ }
232
342
 
233
- ## Optional Next Step
234
- - Immediate next action needed
235
- - Include verbatim quotes from recent conversation if relevant
343
+ formattedSummary = formattedSummary.replace(/\n\n+/g, "\n\n");
236
344
 
237
- Be concise but complete — include information that prevents duplicate work or repeated mistakes.
238
- Respond with text only. Do NOT call any tools.
239
- Wrap your summary in <summary></summary> tags.`;
345
+ return formattedSummary.trim();
346
+ }
240
347
 
241
348
  export const WEB_CONTENT_SYSTEM_PROMPT = `You are a helpful assistant that extracts information from web content. The content is provided in Markdown format.`;
242
349
  export const BTW_SYSTEM_PROMPT = `You are a helpful assistant. Answer the user's side question based on the conversation history.