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.
- package/builtin/plugins/sdd/.wave-plugin/plugin.json +8 -0
- package/builtin/plugins/sdd/hooks/hooks.json +14 -0
- package/builtin/plugins/sdd/scripts/session-start.js +24 -0
- package/builtin/plugins/sdd/scripts/spec-count.js +77 -0
- package/builtin/plugins/sdd/skills/specify/SKILL.md +48 -0
- package/builtin/plugins/sdd/skills/specify/templates/spec-template.md +47 -0
- package/dist/agent.d.ts +8 -0
- package/dist/agent.js +30 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts +18 -0
- package/dist/managers/aiManager.js +155 -46
- package/dist/managers/permissionManager.d.ts +7 -0
- package/dist/managers/permissionManager.js +102 -142
- package/dist/managers/pluginManager.d.ts +7 -0
- package/dist/managers/pluginManager.js +31 -0
- package/dist/managers/subagentManager.js +6 -0
- package/dist/prompts/index.d.ts +12 -1
- package/dist/prompts/index.js +133 -45
- package/dist/services/aiService.d.ts +1 -17
- package/dist/services/aiService.js +3 -85
- package/dist/services/configurationService.d.ts +6 -0
- package/dist/services/configurationService.js +31 -0
- package/dist/services/remoteSettingsService.js +2 -0
- package/dist/services/session.d.ts +3 -1
- package/dist/services/session.js +12 -4
- package/dist/services/taskManager.d.ts +1 -0
- package/dist/services/taskManager.js +34 -5
- package/dist/tools/editTool.js +24 -10
- package/dist/tools/enterWorktreeTool.js +2 -1
- package/dist/tools/grepTool.js +8 -2
- package/dist/tools/writeTool.js +36 -0
- package/dist/types/configuration.d.ts +5 -0
- package/dist/types/permissions.d.ts +0 -2
- package/dist/types/processes.d.ts +27 -0
- package/dist/types/workflow.d.ts +1 -1
- package/dist/utils/bashParser.d.ts +25 -0
- package/dist/utils/bashParser.js +103 -0
- package/dist/utils/configPaths.d.ts +4 -0
- package/dist/utils/configPaths.js +6 -0
- package/dist/utils/containerSetup.js +0 -9
- package/dist/utils/fileSearch.js +4 -2
- package/dist/utils/worktreeSession.d.ts +1 -1
- package/dist/utils/worktreeSession.js +1 -1
- package/dist/utils/worktreeUtils.d.ts +7 -1
- package/dist/utils/worktreeUtils.js +10 -4
- package/dist/workflow/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/agent.ts +29 -10
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +219 -61
- package/src/managers/permissionManager.ts +116 -168
- package/src/managers/pluginManager.ts +29 -0
- package/src/managers/subagentManager.ts +6 -0
- package/src/prompts/index.ts +144 -37
- package/src/services/aiService.ts +9 -128
- package/src/services/configurationService.ts +37 -0
- package/src/services/remoteSettingsService.ts +1 -0
- package/src/services/session.ts +18 -4
- package/src/services/taskManager.ts +46 -7
- package/src/tools/editTool.ts +29 -11
- package/src/tools/enterWorktreeTool.ts +2 -1
- package/src/tools/grepTool.ts +11 -2
- package/src/tools/writeTool.ts +43 -0
- package/src/types/configuration.ts +5 -0
- package/src/types/permissions.ts +0 -2
- package/src/types/processes.ts +29 -0
- package/src/types/workflow.ts +1 -0
- package/src/utils/bashParser.ts +106 -0
- package/src/utils/configPaths.ts +7 -0
- package/src/utils/containerSetup.ts +0 -11
- package/src/utils/fileSearch.ts +6 -2
- package/src/utils/worktreeSession.ts +1 -1
- package/src/utils/worktreeUtils.ts +14 -4
- 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
|
|
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
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
)
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
return false;
|
|
943
|
-
}
|
|
905
|
+
return false;
|
|
906
|
+
}
|
|
944
907
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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
|
-
|
|
926
|
+
const workdir = context.toolInput?.workdir as string | undefined;
|
|
955
927
|
|
|
956
|
-
|
|
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
|
-
|
|
961
|
-
|
|
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
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
940
|
+
if (explicitRules.some((rule) => this.matchesRule(partContext, rule))) {
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
968
943
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
return true;
|
|
962
|
+
return !this.isRestrictedTool(context.toolName);
|
|
963
|
+
});
|
|
977
964
|
}
|
|
978
965
|
|
|
979
|
-
//
|
|
980
|
-
|
|
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
|
|
1000
|
-
const
|
|
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 (
|
|
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 =
|
package/src/prompts/index.ts
CHANGED
|
@@ -190,53 +190,160 @@ export interface SystemPromptBlock {
|
|
|
190
190
|
cacheable: boolean;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
|
|
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
|
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
220
|
-
|
|
311
|
+
if (customInstructions && customInstructions.trim() !== "") {
|
|
312
|
+
prompt += `\n\nAdditional Instructions:\n${customInstructions}`;
|
|
313
|
+
}
|
|
221
314
|
|
|
222
|
-
|
|
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
|
-
|
|
227
|
-
|
|
317
|
+
return prompt;
|
|
318
|
+
}
|
|
228
319
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
-
|
|
238
|
-
|
|
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.
|