wave-agent-sdk 0.16.9 → 0.16.12

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 (85) hide show
  1. package/dist/agent.d.ts +5 -0
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +18 -0
  4. package/dist/constants/toolLimits.d.ts +2 -0
  5. package/dist/constants/toolLimits.d.ts.map +1 -1
  6. package/dist/constants/toolLimits.js +2 -0
  7. package/dist/managers/aiManager.d.ts +5 -0
  8. package/dist/managers/aiManager.d.ts.map +1 -1
  9. package/dist/managers/aiManager.js +21 -0
  10. package/dist/managers/hookManager.d.ts +6 -3
  11. package/dist/managers/hookManager.d.ts.map +1 -1
  12. package/dist/managers/hookManager.js +36 -13
  13. package/dist/managers/mcpManager.d.ts +4 -28
  14. package/dist/managers/mcpManager.d.ts.map +1 -1
  15. package/dist/managers/mcpManager.js +10 -127
  16. package/dist/services/authService.d.ts +33 -1
  17. package/dist/services/authService.d.ts.map +1 -1
  18. package/dist/services/authService.js +212 -11
  19. package/dist/services/configurationService.d.ts +1 -0
  20. package/dist/services/configurationService.d.ts.map +1 -1
  21. package/dist/services/configurationService.js +48 -6
  22. package/dist/services/hook.d.ts +4 -0
  23. package/dist/services/hook.d.ts.map +1 -1
  24. package/dist/services/hook.js +10 -0
  25. package/dist/services/initializationService.d.ts.map +1 -1
  26. package/dist/services/initializationService.js +11 -0
  27. package/dist/services/interactionService.d.ts.map +1 -1
  28. package/dist/services/interactionService.js +0 -12
  29. package/dist/services/remoteSettingsService.d.ts +21 -0
  30. package/dist/services/remoteSettingsService.d.ts.map +1 -0
  31. package/dist/services/remoteSettingsService.js +280 -0
  32. package/dist/tools/bashTool.d.ts.map +1 -1
  33. package/dist/tools/bashTool.js +58 -32
  34. package/dist/tools/types.d.ts +4 -0
  35. package/dist/tools/types.d.ts.map +1 -1
  36. package/dist/types/agent.d.ts +7 -0
  37. package/dist/types/agent.d.ts.map +1 -1
  38. package/dist/types/auth.d.ts +12 -0
  39. package/dist/types/auth.d.ts.map +1 -1
  40. package/dist/types/configuration.d.ts +20 -0
  41. package/dist/types/configuration.d.ts.map +1 -1
  42. package/dist/types/hooks.d.ts +5 -1
  43. package/dist/types/hooks.d.ts.map +1 -1
  44. package/dist/types/hooks.js +1 -0
  45. package/dist/types/mcp.d.ts +1 -1
  46. package/dist/types/mcp.d.ts.map +1 -1
  47. package/dist/utils/containerSetup.d.ts.map +1 -1
  48. package/dist/utils/containerSetup.js +9 -8
  49. package/dist/utils/gitUtils.d.ts +18 -1
  50. package/dist/utils/gitUtils.d.ts.map +1 -1
  51. package/dist/utils/gitUtils.js +120 -49
  52. package/dist/utils/mcpUtils.d.ts.map +1 -1
  53. package/dist/utils/mcpUtils.js +6 -1
  54. package/dist/utils/openaiClient.d.ts.map +1 -1
  55. package/dist/utils/openaiClient.js +4 -2
  56. package/dist/utils/toolResultStorage.d.ts +46 -0
  57. package/dist/utils/toolResultStorage.d.ts.map +1 -0
  58. package/dist/utils/toolResultStorage.js +90 -0
  59. package/dist/utils/worktreeUtils.d.ts.map +1 -1
  60. package/dist/utils/worktreeUtils.js +58 -0
  61. package/package.json +3 -3
  62. package/src/agent.ts +20 -0
  63. package/src/constants/toolLimits.ts +3 -0
  64. package/src/managers/aiManager.ts +37 -0
  65. package/src/managers/hookManager.ts +42 -17
  66. package/src/managers/mcpManager.ts +10 -178
  67. package/src/services/authService.ts +243 -16
  68. package/src/services/configurationService.ts +58 -6
  69. package/src/services/hook.ts +15 -0
  70. package/src/services/initializationService.ts +13 -0
  71. package/src/services/interactionService.ts +0 -18
  72. package/src/services/remoteSettingsService.ts +315 -0
  73. package/src/tools/bashTool.ts +70 -38
  74. package/src/tools/types.ts +4 -0
  75. package/src/types/agent.ts +7 -0
  76. package/src/types/auth.ts +10 -0
  77. package/src/types/configuration.ts +23 -0
  78. package/src/types/hooks.ts +7 -1
  79. package/src/types/mcp.ts +1 -1
  80. package/src/utils/containerSetup.ts +8 -8
  81. package/src/utils/gitUtils.ts +123 -48
  82. package/src/utils/mcpUtils.ts +12 -1
  83. package/src/utils/openaiClient.ts +5 -2
  84. package/src/utils/toolResultStorage.ts +117 -0
  85. package/src/utils/worktreeUtils.ts +63 -0
@@ -29,6 +29,7 @@ import { USER_MEMORY_FILE } from "./constants.js";
29
29
  import { getGitMainRepoRoot } from "./gitUtils.js";
30
30
  import { logger } from "./globalLogger.js";
31
31
  import { authService } from "../services/authService.js";
32
+ import { remoteSettingsService } from "../services/remoteSettingsService.js";
32
33
  export function setupAgentContainer(setupOptions) {
33
34
  const { options, workdir, configurationService, systemPrompt, stream, onBackgroundTasksChange, onTasksChange, onPermissionModeChange, handlePlanModeTransition, setPermissionMode, addPermissionRule, addUsage, } = setupOptions;
34
35
  const callbacks = options.callbacks || {};
@@ -89,24 +90,21 @@ export function setupAgentContainer(setupOptions) {
89
90
  workdir,
90
91
  });
91
92
  container.register("BackgroundTaskManager", backgroundTaskManager);
92
- const ssoToken = authService.getSSOToken();
93
- const serverUrl = options.serverUrl || process.env.WAVE_SERVER_URL;
94
93
  if (options.serverUrl) {
95
94
  authService.setServerUrl(options.serverUrl);
96
95
  }
97
96
  const mcpManager = new McpManager(container, {
98
97
  callbacks,
99
98
  mcpServers: options.mcpServers,
100
- serverUrl,
101
- ssoToken,
102
99
  });
103
100
  container.register("McpManager", mcpManager);
104
- // Wire up auth change callback to reconnect MCP servers after SSO login
101
+ // Wire up auth change callback to refresh/clear remote settings
105
102
  authService.onAuthChange((event) => {
106
103
  if (event === "login") {
107
- const newServerUrl = options.serverUrl || process.env.WAVE_SERVER_URL;
108
- const newToken = authService.getSSOToken();
109
- mcpManager.refreshCredentials(newServerUrl, newToken);
104
+ remoteSettingsService.refresh();
105
+ }
106
+ else if (event === "logout") {
107
+ remoteSettingsService.clear();
110
108
  }
111
109
  });
112
110
  const lspManager = options.lspManager || new LspManager(container);
@@ -128,6 +126,9 @@ export function setupAgentContainer(setupOptions) {
128
126
  const planManager = new PlanManager(container);
129
127
  container.register("PlanManager", planManager);
130
128
  const hookManager = new HookManager(container, workdir);
129
+ if (options.hooks) {
130
+ hookManager.loadConfiguration(options.hooks);
131
+ }
131
132
  container.register("HookManager", hookManager);
132
133
  const skillManager = new SkillManager(container, {
133
134
  workdir,
@@ -23,7 +23,24 @@ export declare function getGitCommonDir(cwd: string): string;
23
23
  */
24
24
  export declare function getGitMainRepoRoot(cwd: string): string;
25
25
  /**
26
- * Get the default remote branch (e.g., origin/main)
26
+ * Resolve the git directory for a repository by walking up from `cwd`.
27
+ * Handles both normal repos (.git is a directory) and worktrees
28
+ * (.git is a file pointing to the main repo's worktree git dir).
29
+ * For worktrees, reads the `commondir` file to find the common git dir.
30
+ * @param cwd Working directory to start searching from
31
+ * @returns Absolute path to the git directory, or null if not found
32
+ */
33
+ export declare function resolveGitDir(cwd: string): string | null;
34
+ /**
35
+ * Get the default remote branch (e.g., origin/main) using filesystem reads.
36
+ * No subprocess calls — matches Claude Code's approach.
37
+ *
38
+ * Priority:
39
+ * 1. Read refs/remotes/origin/HEAD symref → extract branch name (verify it exists)
40
+ * 2. Check if refs/remotes/origin/main ref exists
41
+ * 3. Check if refs/remotes/origin/master ref exists
42
+ * 4. Hardcoded "main" fallback
43
+ *
27
44
  * @param cwd Working directory
28
45
  * @returns Default remote branch name
29
46
  */
@@ -1 +1 @@
1
- {"version":3,"file":"gitUtils.d.ts","sourceRoot":"","sources":["../../src/utils/gitUtils.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAevD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2D1D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW1D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAYvE"}
1
+ {"version":3,"file":"gitUtils.d.ts","sourceRoot":"","sources":["../../src/utils/gitUtils.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAevD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAetD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCxD;AAwDD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA4B1D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAW1D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAYvE"}
@@ -81,69 +81,140 @@ export function getGitMainRepoRoot(cwd) {
81
81
  }
82
82
  }
83
83
  /**
84
- * Get the default remote branch (e.g., origin/main)
85
- * @param cwd Working directory
86
- * @returns Default remote branch name
84
+ * Resolve the git directory for a repository by walking up from `cwd`.
85
+ * Handles both normal repos (.git is a directory) and worktrees
86
+ * (.git is a file pointing to the main repo's worktree git dir).
87
+ * For worktrees, reads the `commondir` file to find the common git dir.
88
+ * @param cwd Working directory to start searching from
89
+ * @returns Absolute path to the git directory, or null if not found
87
90
  */
88
- export function getDefaultRemoteBranch(cwd) {
89
- // 1. Try git symbolic-ref refs/remotes/origin/HEAD
90
- try {
91
- const head = execSync("git symbolic-ref refs/remotes/origin/HEAD", {
92
- cwd,
93
- encoding: "utf8",
94
- stdio: ["ignore", "pipe", "ignore"],
95
- }).trim();
96
- return head.replace("refs/remotes/", "");
97
- }
98
- catch {
99
- // Ignore error and proceed to next step
91
+ export function resolveGitDir(cwd) {
92
+ let currentPath = path.resolve(cwd);
93
+ while (currentPath !== path.dirname(currentPath)) {
94
+ const gitPath = path.join(currentPath, ".git");
95
+ if (fsSync.existsSync(gitPath)) {
96
+ const stat = fsSync.statSync(gitPath);
97
+ if (stat.isDirectory()) {
98
+ // Normal repo: .git is a directory
99
+ return gitPath;
100
+ }
101
+ // Worktree: .git is a file containing "gitdir: <path>"
102
+ try {
103
+ const content = fsSync.readFileSync(gitPath, "utf8").trim();
104
+ const prefix = "gitdir: ";
105
+ if (content.startsWith(prefix)) {
106
+ const worktreeGitDir = content.substring(prefix.length);
107
+ // Read commondir to find the common git dir
108
+ const commondirPath = path.join(worktreeGitDir, "commondir");
109
+ if (fsSync.existsSync(commondirPath)) {
110
+ const commondirRel = fsSync
111
+ .readFileSync(commondirPath, "utf8")
112
+ .trim();
113
+ return path.resolve(worktreeGitDir, commondirRel);
114
+ }
115
+ // Fallback: resolve ../.. from the worktree git dir
116
+ return path.resolve(worktreeGitDir, "..", "..");
117
+ }
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ currentPath = path.dirname(currentPath);
100
124
  }
101
- // 2. Check if origin/main exists
125
+ return null;
126
+ }
127
+ /**
128
+ * Read a symbolic ref file in the git directory.
129
+ * If the file content starts with "ref: ", returns the target ref path.
130
+ * Symbolic refs are never packed in packed-refs, so only loose refs are checked.
131
+ * @param gitDir Absolute path to the git directory
132
+ * @param refPath Relative path to the ref file (e.g., "refs/remotes/origin/HEAD")
133
+ * @returns The symbolic ref target, or null if not a symbolic ref or on error
134
+ */
135
+ function readSymref(gitDir, refPath) {
102
136
  try {
103
- execSync("git rev-parse --verify origin/main", {
104
- cwd,
105
- stdio: "ignore",
106
- });
107
- return "origin/main";
137
+ const fullPath = path.join(gitDir, refPath);
138
+ const content = fsSync.readFileSync(fullPath, "utf8").trim();
139
+ if (content.startsWith("ref: ")) {
140
+ return content.substring("ref: ".length);
141
+ }
142
+ return null;
108
143
  }
109
144
  catch {
110
- // Ignore error and proceed to next step
145
+ return null;
111
146
  }
112
- // 3. Check if origin/master exists
147
+ }
148
+ /**
149
+ * Check if a git ref exists, checking both loose refs and packed-refs.
150
+ * @param gitDir Absolute path to the git directory
151
+ * @param refPath Relative path to the ref (e.g., "refs/remotes/origin/main")
152
+ * @returns True if the ref exists, false otherwise
153
+ */
154
+ function refExists(gitDir, refPath) {
155
+ // 1. Check loose ref
156
+ const loosePath = path.join(gitDir, refPath);
157
+ if (fsSync.existsSync(loosePath)) {
158
+ return true;
159
+ }
160
+ // 2. Check packed-refs
113
161
  try {
114
- execSync("git rev-parse --verify origin/master", {
115
- cwd,
116
- stdio: "ignore",
117
- });
118
- return "origin/master";
162
+ const packedRefsPath = path.join(gitDir, "packed-refs");
163
+ const content = fsSync.readFileSync(packedRefsPath, "utf8");
164
+ for (const line of content.split("\n")) {
165
+ // Skip comments and peeled lines
166
+ if (line.startsWith("#") || line.startsWith("^")) {
167
+ continue;
168
+ }
169
+ // Format: <sha> <refPath>
170
+ const parts = line.split(" ");
171
+ if (parts.length >= 2 && parts[1] === refPath) {
172
+ return true;
173
+ }
174
+ }
119
175
  }
120
176
  catch {
121
- // Ignore error and proceed to next step
177
+ // packed-refs doesn't exist or can't be read
122
178
  }
123
- // 4. Try to get the current branch's upstream
124
- try {
125
- return execSync("git rev-parse --abbrev-ref --symbolic-full-name @{u}", {
126
- cwd,
127
- encoding: "utf8",
128
- stdio: ["ignore", "pipe", "ignore"],
129
- }).trim();
179
+ return false;
180
+ }
181
+ /**
182
+ * Get the default remote branch (e.g., origin/main) using filesystem reads.
183
+ * No subprocess calls — matches Claude Code's approach.
184
+ *
185
+ * Priority:
186
+ * 1. Read refs/remotes/origin/HEAD symref → extract branch name (verify it exists)
187
+ * 2. Check if refs/remotes/origin/main ref exists
188
+ * 3. Check if refs/remotes/origin/master ref exists
189
+ * 4. Hardcoded "main" fallback
190
+ *
191
+ * @param cwd Working directory
192
+ * @returns Default remote branch name
193
+ */
194
+ export function getDefaultRemoteBranch(cwd) {
195
+ const gitDir = resolveGitDir(cwd);
196
+ if (!gitDir) {
197
+ return "main";
130
198
  }
131
- catch {
132
- // Ignore error and proceed to next step
199
+ // 1. Try reading origin/HEAD symref
200
+ const symref = readSymref(gitDir, "refs/remotes/origin/HEAD");
201
+ if (symref) {
202
+ const branch = symref.replace("refs/remotes/", "");
203
+ // Verify the resolved branch actually exists (origin/HEAD can be stale)
204
+ if (refExists(gitDir, symref)) {
205
+ return branch;
206
+ }
133
207
  }
134
- // 5. Try to get the current branch name
135
- try {
136
- return execSync("git rev-parse --abbrev-ref HEAD", {
137
- cwd,
138
- encoding: "utf8",
139
- stdio: ["ignore", "pipe", "ignore"],
140
- }).trim();
208
+ // 2. Check if origin/main exists
209
+ if (refExists(gitDir, "refs/remotes/origin/main")) {
210
+ return "origin/main";
141
211
  }
142
- catch {
143
- // Ignore error and proceed to next step
212
+ // 3. Check if origin/master exists
213
+ if (refExists(gitDir, "refs/remotes/origin/master")) {
214
+ return "origin/master";
144
215
  }
145
- // 6. Fallback to origin/main
146
- return "origin/main";
216
+ // 4. Hardcoded fallback
217
+ return "main";
147
218
  }
148
219
  /**
149
220
  * Check if there are uncommitted changes in the working directory
@@ -1 +1 @@
1
- {"version":3,"file":"mcpUtils.d.ts","sourceRoot":"","sources":["../../src/utils/mcpUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA8ClE;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,0BAA0B,CAgB5B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,CACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,WAAW,KAClB,OAAO,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD,CAAC,GACD,UAAU,CAwBZ;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EAAE,GACzB,eAAe,GAAG,SAAS,CAK7B"}
1
+ {"version":3,"file":"mcpUtils.d.ts","sourceRoot":"","sources":["../../src/utils/mcpUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,UAAU,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAgDlE;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,0BAA0B,CAgB5B;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,CACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,WAAW,KAClB,OAAO,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD,CAAC,GACD,UAAU,CAiCZ;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EAAE,GACzB,eAAe,GAAG,SAAS,CAK7B"}
@@ -1,3 +1,5 @@
1
+ import { processToolResult } from "./toolResultStorage.js";
2
+ import { DEFAULT_MAX_RESULT_SIZE_CHARS } from "../constants/toolLimits.js";
1
3
  /**
2
4
  * Recursively clean schema to remove unsupported fields
3
5
  */
@@ -60,9 +62,12 @@ export function createMcpToolPlugin(mcpTool, serverName, executeTool) {
60
62
  async execute(args, context) {
61
63
  try {
62
64
  const result = await executeTool(prefixedName, args, context);
65
+ // Process content for size limits — only text content, not images
66
+ const processedContent = processToolResult(result.content || `Executed ${mcpTool.name}`, DEFAULT_MAX_RESULT_SIZE_CHARS, `mcp_${serverName}_${mcpTool.name}`);
63
67
  return {
64
68
  success: true,
65
- content: result.content || `Executed ${mcpTool.name}`,
69
+ content: processedContent,
70
+ images: result.images,
66
71
  };
67
72
  }
68
73
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"openaiClient.d.ts","sourceRoot":"","sources":["../../src/utils/openaiClient.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sCAAsC,EACtC,mCAAmC,EACnC,mBAAmB,EACnB,cAAc,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,KAAK,YAAY,GACb,sCAAsC,GACtC,mCAAmC,CAAC;AAExC,UAAU,WAAW,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,UAAU,UAAU,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC;IACxC,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CACzC;AAED,qBAAa,YAAY;IACX,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEzC,IAAI,IAAI;;qBAGO,CAAC,SAAS,YAAY,UACrB,CAAC,YACC;gBAAE,MAAM,CAAC,EAAE,WAAW,CAAA;aAAE,KACjC,UAAU,CACX,CAAC,SAAS,mCAAmC,GACzC,aAAa,CAAC,mBAAmB,CAAC,GAClC,cAAc,CACnB;;MA2BN;YAEa,OAAO;YAqIN,oBAAoB;CAqCpC"}
1
+ {"version":3,"file":"openaiClient.d.ts","sourceRoot":"","sources":["../../src/utils/openaiClient.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sCAAsC,EACtC,mCAAmC,EACnC,mBAAmB,EACnB,cAAc,EACf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,KAAK,YAAY,GACb,sCAAsC,GACtC,mCAAmC,CAAC;AAExC,UAAU,WAAW,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,UAAU,UAAU,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC;IACxC,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CACzC;AAED,qBAAa,YAAY;IACX,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEzC,IAAI,IAAI;;qBAGO,CAAC,SAAS,YAAY,UACrB,CAAC,YACC;gBAAE,MAAM,CAAC,EAAE,WAAW,CAAA;aAAE,KACjC,UAAU,CACX,CAAC,SAAS,mCAAmC,GACzC,aAAa,CAAC,mBAAmB,CAAC,GAClC,cAAc,CACnB;;MA2BN;YAEa,OAAO;YAwIN,oBAAoB;CAqCpC"}
@@ -107,8 +107,10 @@ export class OpenAIClient {
107
107
  : response.statusText);
108
108
  error.status = response.status;
109
109
  error.body = errorBody;
110
- if (response.status === 429 && attempt < maxRetries) {
111
- logger.warn("OpenAI API 429 Too Many Requests, retrying...", {
110
+ const retryableStatus = response.status === 429 ||
111
+ (response.status >= 500 && response.status !== 501);
112
+ if (retryableStatus && attempt < maxRetries) {
113
+ logger.warn("OpenAI API error, retrying...", {
112
114
  attempt: attempt + 1,
113
115
  status: response.status,
114
116
  });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared tool result persistence and truncation logic.
3
+ *
4
+ * When a tool result exceeds a size threshold, the full content is saved to a
5
+ * file in /tmp/wave-tool-results/ and the model receives a <persisted-output>
6
+ * preview with the file path so it can use the Read tool to access the full output.
7
+ */
8
+ /**
9
+ * Get (and create if needed) the tool-results directory.
10
+ * Uses /tmp/wave-tool-results/ for simplicity and automatic OS cleanup.
11
+ */
12
+ export declare function getToolResultsDir(): string;
13
+ /**
14
+ * Persist full tool output to a file in the tool-results directory.
15
+ * Returns the file path on success, or undefined on failure.
16
+ */
17
+ export declare function persistToolResult(content: string, prefix?: string): string | undefined;
18
+ /**
19
+ * Generate a preview from content: first `previewSize` characters with ellipsis.
20
+ */
21
+ export declare function generatePreview(content: string, previewSize?: number): string;
22
+ /**
23
+ * Build the <persisted-output> wrapper message that the model sees.
24
+ *
25
+ * Example output:
26
+ * <persisted-output>
27
+ * Output too large (150,000 characters). Full output saved to: /tmp/wave-tool-results/mcp_server_tool_12345.txt
28
+ * Preview (first 2,048 characters):
29
+ * {preview content}
30
+ * ...
31
+ * </persisted-output>
32
+ */
33
+ export declare function buildPersistedOutputMessage(totalChars: number, filePath: string, preview: string): string;
34
+ /**
35
+ * Process tool result: if content exceeds maxChars, persist to file and return
36
+ * truncated content with <persisted-output> wrapper. Otherwise return unchanged.
37
+ *
38
+ * This is the main entry point for both MCP and bash tools.
39
+ *
40
+ * @param content - The tool result content
41
+ * @param maxChars - Maximum characters before persistence kicks in (defaults to DEFAULT_MAX_RESULT_SIZE_CHARS)
42
+ * @param prefix - File name prefix for the persisted file (e.g. "bash", "mcp")
43
+ * @returns The content to send to the model (either original or persisted-output wrapper)
44
+ */
45
+ export declare function processToolResult(content: string, maxChars?: number, prefix?: string): string;
46
+ //# sourceMappingURL=toolResultStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolResultStorage.d.ts","sourceRoot":"","sources":["../../src/utils/toolResultStorage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAe,GACtB,MAAM,GAAG,SAAS,CAWpB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,MAA2B,GACvC,MAAM,CAGR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,MAAM,CAQR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAAsC,EAChD,MAAM,GAAE,MAAe,GACtB,MAAM,CAiBR"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Shared tool result persistence and truncation logic.
3
+ *
4
+ * When a tool result exceeds a size threshold, the full content is saved to a
5
+ * file in /tmp/wave-tool-results/ and the model receives a <persisted-output>
6
+ * preview with the file path so it can use the Read tool to access the full output.
7
+ */
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ import * as os from "os";
11
+ import { DEFAULT_MAX_RESULT_SIZE_CHARS, PREVIEW_SIZE_BYTES, } from "../constants/toolLimits.js";
12
+ import { logger } from "./globalLogger.js";
13
+ const TOOL_RESULTS_DIR = path.join(os.tmpdir(), "wave-tool-results");
14
+ /**
15
+ * Get (and create if needed) the tool-results directory.
16
+ * Uses /tmp/wave-tool-results/ for simplicity and automatic OS cleanup.
17
+ */
18
+ export function getToolResultsDir() {
19
+ fs.mkdirSync(TOOL_RESULTS_DIR, { recursive: true });
20
+ return TOOL_RESULTS_DIR;
21
+ }
22
+ /**
23
+ * Persist full tool output to a file in the tool-results directory.
24
+ * Returns the file path on success, or undefined on failure.
25
+ */
26
+ export function persistToolResult(content, prefix = "tool") {
27
+ try {
28
+ const dir = getToolResultsDir();
29
+ const id = `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
30
+ const filePath = path.join(dir, `${id}.txt`);
31
+ fs.writeFileSync(filePath, content, "utf8");
32
+ return filePath;
33
+ }
34
+ catch (error) {
35
+ logger?.error("Failed to persist tool result:", error);
36
+ return undefined;
37
+ }
38
+ }
39
+ /**
40
+ * Generate a preview from content: first `previewSize` characters with ellipsis.
41
+ */
42
+ export function generatePreview(content, previewSize = PREVIEW_SIZE_BYTES) {
43
+ if (content.length <= previewSize)
44
+ return content;
45
+ return content.substring(0, previewSize) + "\n...";
46
+ }
47
+ /**
48
+ * Build the <persisted-output> wrapper message that the model sees.
49
+ *
50
+ * Example output:
51
+ * <persisted-output>
52
+ * Output too large (150,000 characters). Full output saved to: /tmp/wave-tool-results/mcp_server_tool_12345.txt
53
+ * Preview (first 2,048 characters):
54
+ * {preview content}
55
+ * ...
56
+ * </persisted-output>
57
+ */
58
+ export function buildPersistedOutputMessage(totalChars, filePath, preview) {
59
+ return [
60
+ "<persisted-output>",
61
+ `Output too large (${totalChars.toLocaleString()} characters). Full output saved to: ${filePath}`,
62
+ `Preview (first ${PREVIEW_SIZE_BYTES.toLocaleString()} characters):`,
63
+ preview,
64
+ "</persisted-output>",
65
+ ].join("\n");
66
+ }
67
+ /**
68
+ * Process tool result: if content exceeds maxChars, persist to file and return
69
+ * truncated content with <persisted-output> wrapper. Otherwise return unchanged.
70
+ *
71
+ * This is the main entry point for both MCP and bash tools.
72
+ *
73
+ * @param content - The tool result content
74
+ * @param maxChars - Maximum characters before persistence kicks in (defaults to DEFAULT_MAX_RESULT_SIZE_CHARS)
75
+ * @param prefix - File name prefix for the persisted file (e.g. "bash", "mcp")
76
+ * @returns The content to send to the model (either original or persisted-output wrapper)
77
+ */
78
+ export function processToolResult(content, maxChars = DEFAULT_MAX_RESULT_SIZE_CHARS, prefix = "tool") {
79
+ if (content.length <= maxChars) {
80
+ return content;
81
+ }
82
+ const filePath = persistToolResult(content, prefix);
83
+ if (filePath) {
84
+ const preview = generatePreview(content);
85
+ return buildPersistedOutputMessage(content.length, filePath, preview);
86
+ }
87
+ // Fallback: truncation only (persistence failed)
88
+ return (content.substring(0, maxChars) +
89
+ "\n\n... (output truncated, failed to persist full output)");
90
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"worktreeUtils.d.ts","sourceRoot":"","sources":["../../src/utils/worktreeUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBvD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CA6B7C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAwFtE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA+DvD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACrC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA8BlD"}
1
+ {"version":3,"file":"worktreeUtils.d.ts","sourceRoot":"","sources":["../../src/utils/worktreeUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBvD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CA6B7C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,CAuJtE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CA+DvD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,GAAG,SAAS,GACrC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA8BlD"}
@@ -142,6 +142,64 @@ export function createWorktree(name, cwd) {
142
142
  throw new Error(`Failed to add worktree: ${innerError.message}`);
143
143
  }
144
144
  }
145
+ if (stderr.includes("not a valid object name") ||
146
+ stderr.includes("unknown revision")) {
147
+ // Base branch not fetched yet — try fetching then retrying
148
+ const branchNameOnly = baseBranch.split("/").pop();
149
+ try {
150
+ execSync(`git fetch origin ${branchNameOnly}`, {
151
+ cwd: repoRoot,
152
+ stdio: ["ignore", "pipe", "pipe"],
153
+ env: {
154
+ ...process.env,
155
+ GIT_TERMINAL_PROMPT: "0",
156
+ GIT_ASKPASS: "",
157
+ },
158
+ });
159
+ execSync(`git worktree add -b ${branchName} "${worktreePath}" ${baseBranch}`, {
160
+ cwd: repoRoot,
161
+ stdio: ["ignore", "pipe", "pipe"],
162
+ env: {
163
+ ...process.env,
164
+ GIT_TERMINAL_PROMPT: "0",
165
+ GIT_ASKPASS: "",
166
+ },
167
+ });
168
+ return {
169
+ name,
170
+ path: worktreePath,
171
+ branch: branchName,
172
+ repoRoot,
173
+ isNew: true,
174
+ originalHeadCommit,
175
+ };
176
+ }
177
+ catch {
178
+ // Fetch or retry failed — fall back to HEAD
179
+ try {
180
+ execSync(`git worktree add -b ${branchName} "${worktreePath}" HEAD`, {
181
+ cwd: repoRoot,
182
+ stdio: ["ignore", "pipe", "pipe"],
183
+ env: {
184
+ ...process.env,
185
+ GIT_TERMINAL_PROMPT: "0",
186
+ GIT_ASKPASS: "",
187
+ },
188
+ });
189
+ return {
190
+ name,
191
+ path: worktreePath,
192
+ branch: branchName,
193
+ repoRoot,
194
+ isNew: true,
195
+ originalHeadCommit,
196
+ };
197
+ }
198
+ catch {
199
+ throw new Error(`Failed to create worktree: ${error.message}\n${stderr}`);
200
+ }
201
+ }
202
+ }
145
203
  throw new Error(`Failed to create worktree: ${error.message}\n${stderr}`);
146
204
  }
147
205
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.16.9",
3
+ "version": "0.16.12",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -48,11 +48,11 @@
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/turndown": "^5.0.6",
51
- "@vitest/coverage-v8": "^4.0.18",
51
+ "@vitest/coverage-v8": "^4.1.7",
52
52
  "rimraf": "^6.1.2",
53
53
  "tsc-alias": "^1.8.16",
54
54
  "tsx": "^4.20.4",
55
- "vitest": "^4.0.18"
55
+ "vitest": "^4.1.7"
56
56
  },
57
57
  "engines": {
58
58
  "node": ">=22.0.0"
package/src/agent.ts CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  shutdownTelemetry,
52
52
  } from "./telemetry/instrumentation.js";
53
53
  import { logOTelEvent } from "./telemetry/events.js";
54
+ import { remoteSettingsService } from "./services/remoteSettingsService.js";
54
55
 
55
56
  export class Agent {
56
57
  private messageManager: MessageManager;
@@ -220,6 +221,13 @@ export class Agent {
220
221
  }
221
222
  };
222
223
 
224
+ // Wire up CWD change callback from AIManager to sync Agent's workdir
225
+ this.aiManager.setOnCwdChange((newCwd) => {
226
+ this.workdir = newCwd;
227
+ this.container.register("Workdir", newCwd);
228
+ this.options.callbacks?.onWorkdirChange?.(newCwd);
229
+ });
230
+
223
231
  // Wire up message queue to process when agent becomes idle
224
232
  this.messageQueue.onMessageEnqueued = () => {
225
233
  // If the AI is NOT loading and command is not running, trigger dequeue
@@ -282,6 +290,16 @@ export class Agent {
282
290
  return this.workdir;
283
291
  }
284
292
 
293
+ /**
294
+ * Set the working directory
295
+ * @param newCwd - The new working directory
296
+ */
297
+ public setWorkdir(newCwd: string): void {
298
+ this.workdir = newCwd;
299
+ this.container.register("Workdir", newCwd);
300
+ this.options.callbacks?.onWorkdirChange?.(newCwd);
301
+ }
302
+
285
303
  /** Get project memory content */
286
304
  public get projectMemory(): string {
287
305
  const memoryService =
@@ -720,6 +738,8 @@ export class Agent {
720
738
  error,
721
739
  );
722
740
  }
741
+ // Cleanup remote settings polling
742
+ remoteSettingsService.shutdown();
723
743
  // Cleanup memory store
724
744
  }
725
745
 
@@ -5,6 +5,9 @@
5
5
  /** System-wide default max result size in characters. */
6
6
  export const DEFAULT_MAX_RESULT_SIZE_CHARS = 50_000;
7
7
 
8
+ /** Per-command cap for bash tool output before persistence. */
9
+ export const BASH_MAX_OUTPUT_CHARS = 30_000;
10
+
8
11
  /** Per-command cap for skill bash substitution (inline/block). */
9
12
  export const SKILL_BASH_MAX_OUTPUT_CHARS = 30_000;
10
13