wave-agent-sdk 0.18.6 → 0.18.7

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 (51) hide show
  1. package/dist/managers/aiManager.d.ts.map +1 -1
  2. package/dist/managers/aiManager.js +12 -5
  3. package/dist/managers/cronManager.d.ts.map +1 -1
  4. package/dist/managers/cronManager.js +2 -0
  5. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  6. package/dist/managers/liveConfigManager.js +0 -9
  7. package/dist/managers/mcpManager.d.ts.map +1 -1
  8. package/dist/managers/mcpManager.js +6 -1
  9. package/dist/managers/messageManager.d.ts +1 -1
  10. package/dist/managers/messageManager.d.ts.map +1 -1
  11. package/dist/managers/messageManager.js +10 -4
  12. package/dist/managers/toolManager.d.ts.map +1 -1
  13. package/dist/managers/toolManager.js +2 -0
  14. package/dist/services/configurationService.d.ts.map +1 -1
  15. package/dist/services/configurationService.js +2 -0
  16. package/dist/telemetry/sessionTracing.d.ts +17 -16
  17. package/dist/telemetry/sessionTracing.d.ts.map +1 -1
  18. package/dist/telemetry/sessionTracing.js +62 -58
  19. package/dist/tools/readTool.d.ts.map +1 -1
  20. package/dist/tools/readTool.js +6 -7
  21. package/dist/types/telemetry.d.ts +2 -0
  22. package/dist/types/telemetry.d.ts.map +1 -1
  23. package/dist/utils/fileUtils.d.ts +0 -6
  24. package/dist/utils/fileUtils.d.ts.map +1 -1
  25. package/dist/utils/fileUtils.js +0 -43
  26. package/dist/utils/gitUtils.d.ts +11 -0
  27. package/dist/utils/gitUtils.d.ts.map +1 -1
  28. package/dist/utils/gitUtils.js +51 -0
  29. package/dist/utils/groupMessagesByApiRound.d.ts.map +1 -1
  30. package/dist/utils/groupMessagesByApiRound.js +7 -6
  31. package/dist/utils/tokenEstimate.d.ts +24 -0
  32. package/dist/utils/tokenEstimate.d.ts.map +1 -0
  33. package/dist/utils/tokenEstimate.js +30 -0
  34. package/dist/utils/worktreeUtils.d.ts.map +1 -1
  35. package/dist/utils/worktreeUtils.js +3 -1
  36. package/package.json +1 -1
  37. package/src/managers/aiManager.ts +14 -5
  38. package/src/managers/cronManager.ts +2 -0
  39. package/src/managers/liveConfigManager.ts +0 -12
  40. package/src/managers/mcpManager.ts +8 -1
  41. package/src/managers/messageManager.ts +10 -5
  42. package/src/managers/toolManager.ts +2 -0
  43. package/src/services/configurationService.ts +2 -0
  44. package/src/telemetry/sessionTracing.ts +64 -67
  45. package/src/tools/readTool.ts +6 -12
  46. package/src/types/telemetry.ts +2 -0
  47. package/src/utils/fileUtils.ts +0 -46
  48. package/src/utils/gitUtils.ts +54 -0
  49. package/src/utils/groupMessagesByApiRound.ts +7 -6
  50. package/src/utils/tokenEstimate.ts +34 -0
  51. package/src/utils/worktreeUtils.ts +8 -1
@@ -63,6 +63,8 @@ export interface ToolMetadata {
63
63
  error?: string;
64
64
  /** Execution time (ms) */
65
65
  durationMs: number;
66
+ /** Tool output content (recorded when logToolContent is true) */
67
+ output?: string;
66
68
  }
67
69
  /** Event names for OTel structured logging */
68
70
  export type OTelEventName = "session_start" | "session_end" | "user_prompt" | "tool_decision" | "compaction" | "error";
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/types/telemetry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,CAAC;AAE9C,8BAA8B;AAC9B,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,WAAW,GAAG,MAAM,CAAC;AAElE,uCAAuC;AACvC,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,0BAA0B;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,wBAAwB;IACxB,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,0BAA0B;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oCAAoC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,iCAAiC;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,4CAA4C;IAC5C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qCAAqC;AACrC,MAAM,WAAW,kBAAkB;IACjC,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,OAAO,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACvC,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yBAAyB;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+BAA+B;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,8CAA8C;AAC9C,MAAM,MAAM,aAAa,GACrB,eAAe,GACf,aAAa,GACb,aAAa,GACb,eAAe,GACf,YAAY,GACZ,OAAO,CAAC"}
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/types/telemetry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,MAAM,CAAC;AAE9C,8BAA8B;AAC9B,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,WAAW,GAAG,MAAM,CAAC;AAElE,uCAAuC;AACvC,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,0BAA0B;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,wBAAwB;IACxB,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,0BAA0B;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,oCAAoC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,iCAAiC;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,4CAA4C;IAC5C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qCAAqC;AACrC,MAAM,WAAW,kBAAkB;IACjC,uBAAuB;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,OAAO,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACvC,6BAA6B;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yBAAyB;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,+BAA+B;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,8CAA8C;AAC9C,MAAM,MAAM,aAAa,GACrB,eAAe,GACf,aAAa,GACb,aAAa,GACb,eAAe,GACf,YAAY,GACZ,OAAO,CAAC"}
@@ -26,12 +26,6 @@ export declare function readFirstNLines(filePath: string, maxLines: number): Pro
26
26
  * @return {Promise<string>} - The last non-empty line of the file, or an empty string if no non-empty lines found.
27
27
  */
28
28
  export declare function getLastLine(filePath: string, minLength?: number): Promise<string>;
29
- /**
30
- * Ensures that a pattern is present in the global git ignore file.
31
- *
32
- * @param {string} pattern - The pattern to add to global git ignore.
33
- */
34
- export declare function ensureGlobalGitIgnore(pattern: string): Promise<void>;
35
29
  /**
36
30
  * Suggests similar paths if a file is not found.
37
31
  */
@@ -1 +1 @@
1
- {"version":3,"file":"fileUtils.d.ts","sourceRoot":"","sources":["../../src/utils/fileUtils.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBrE;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,EAAE,CAAC,CA4BnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAI,GACZ,OAAO,CAAC,MAAM,CAAC,CA8DjB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqC1E;AA2BD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBnB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB"}
1
+ {"version":3,"file":"fileUtils.d.ts","sourceRoot":"","sources":["../../src/utils/fileUtils.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBrE;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,EAAE,CAAC,CA4BnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAI,GACZ,OAAO,CAAC,MAAM,CAAC,CA8DjB;AA2BD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,EAAE,CAAC,CAwBnB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB"}
@@ -1,8 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import { createReadStream } from "node:fs";
3
3
  import path from "node:path";
4
- import { execSync } from "node:child_process";
5
- import { homedir } from "node:os";
6
4
  import { glob } from "glob";
7
5
  /**
8
6
  * Reads the first line of a file efficiently using Node.js readline.
@@ -138,47 +136,6 @@ export async function getLastLine(filePath, minLength = 1) {
138
136
  }
139
137
  }
140
138
  }
141
- /**
142
- * Ensures that a pattern is present in the global git ignore file.
143
- *
144
- * @param {string} pattern - The pattern to add to global git ignore.
145
- */
146
- export async function ensureGlobalGitIgnore(pattern) {
147
- try {
148
- let globalIgnorePath;
149
- try {
150
- globalIgnorePath = execSync("git config --get core.excludesfile", {
151
- encoding: "utf8",
152
- }).trim();
153
- }
154
- catch {
155
- // If not set, use default paths
156
- const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(homedir(), ".config");
157
- globalIgnorePath = path.join(xdgConfigHome, "git", "ignore");
158
- }
159
- if (!globalIgnorePath)
160
- return;
161
- // Ensure directory exists
162
- await fs.mkdir(path.dirname(globalIgnorePath), { recursive: true });
163
- let content = "";
164
- try {
165
- content = await fs.readFile(globalIgnorePath, "utf8");
166
- }
167
- catch {
168
- // File doesn't exist
169
- }
170
- const lines = content.split("\n").map((line) => line.trim());
171
- if (!lines.includes(pattern)) {
172
- const newContent = content.endsWith("\n") || content === ""
173
- ? `${content}${pattern}\n`
174
- : `${content}\n${pattern}\n`;
175
- await fs.writeFile(globalIgnorePath, newContent, "utf8");
176
- }
177
- }
178
- catch {
179
- // Ignore errors
180
- }
181
- }
182
139
  /**
183
140
  * Simple Levenshtein distance implementation
184
141
  */
@@ -45,6 +45,17 @@ export declare function resolveGitDir(cwd: string): string | null;
45
45
  * @returns Default remote branch name
46
46
  */
47
47
  export declare function getDefaultRemoteBranch(cwd: string): string;
48
+ /**
49
+ * Ensure that Wave runtime files are excluded from git status by writing
50
+ * patterns to `.git/info/exclude` (per-repo, not global).
51
+ *
52
+ * Idempotent: if the marker `# wave-runtime` is already present in the
53
+ * exclude file, it skips writing. A module-level Set provides additional
54
+ * per-process dedup to avoid redundant file reads.
55
+ *
56
+ * @param cwd Working directory to start searching from
57
+ */
58
+ export declare function ensureWaveRuntimeFilesExcluded(cwd: string): void;
48
59
  /**
49
60
  * Check if there are uncommitted changes in the working directory
50
61
  * @param cwd Working directory
@@ -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;;;;;;;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"}
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;AAQD;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAoChE;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"}
@@ -216,6 +216,57 @@ export function getDefaultRemoteBranch(cwd) {
216
216
  // 4. Hardcoded fallback
217
217
  return "main";
218
218
  }
219
+ /**
220
+ * Module-level set tracking git dirs already processed in this process,
221
+ * to avoid redundant I/O.
222
+ */
223
+ const excludedGitDirs = new Set();
224
+ /**
225
+ * Ensure that Wave runtime files are excluded from git status by writing
226
+ * patterns to `.git/info/exclude` (per-repo, not global).
227
+ *
228
+ * Idempotent: if the marker `# wave-runtime` is already present in the
229
+ * exclude file, it skips writing. A module-level Set provides additional
230
+ * per-process dedup to avoid redundant file reads.
231
+ *
232
+ * @param cwd Working directory to start searching from
233
+ */
234
+ export function ensureWaveRuntimeFilesExcluded(cwd) {
235
+ try {
236
+ const gitDir = resolveGitDir(cwd);
237
+ if (!gitDir)
238
+ return;
239
+ if (excludedGitDirs.has(gitDir))
240
+ return;
241
+ const excludePath = path.join(gitDir, "info", "exclude");
242
+ let content = "";
243
+ try {
244
+ content = fsSync.readFileSync(excludePath, "utf8");
245
+ }
246
+ catch {
247
+ // File doesn't exist yet
248
+ }
249
+ const marker = "# wave-runtime";
250
+ if (content.includes(marker)) {
251
+ excludedGitDirs.add(gitDir);
252
+ return;
253
+ }
254
+ const block = [
255
+ marker,
256
+ "**/.wave/scheduled_tasks.lock",
257
+ "**/.wave/scheduled_tasks.json",
258
+ "**/.wave/worktrees/",
259
+ "**/.wave/settings.local.json",
260
+ "",
261
+ ].join("\n");
262
+ fsSync.mkdirSync(path.join(gitDir, "info"), { recursive: true });
263
+ fsSync.appendFileSync(excludePath, block);
264
+ excludedGitDirs.add(gitDir);
265
+ }
266
+ catch {
267
+ // Best-effort: ignore all errors
268
+ }
269
+ }
219
270
  /**
220
271
  * Check if there are uncommitted changes in the working directory
221
272
  * @param cwd Working directory
@@ -1 +1 @@
1
- {"version":3,"file":"groupMessagesByApiRound.d.ts","sourceRoot":"","sources":["../../src/utils/groupMessagesByApiRound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,CA8DvE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE,MAAM,GACjB,OAAO,EAAE,CAIX"}
1
+ {"version":3,"file":"groupMessagesByApiRound.d.ts","sourceRoot":"","sources":["../../src/utils/groupMessagesByApiRound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,QAAQ,EAAE,CA8DvE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,UAAU,EAAE,MAAM,GACjB,OAAO,EAAE,CAIX"}
@@ -1,3 +1,4 @@
1
+ import { estimateTokens as estimateStrTokens } from "./tokenEstimate.js";
1
2
  /**
2
3
  * Groups messages into "API rounds" — each round corresponds to one API
3
4
  * call-response cycle. This is critical because in agentic sessions with a
@@ -74,24 +75,24 @@ export function getLastApiRounds(messages, roundCount) {
74
75
  return lastRounds.flatMap((r) => r.messages);
75
76
  }
76
77
  /**
77
- * Roughly estimate token count from character count (~4 chars per token).
78
+ * Estimate token count from message blocks using CJK-aware estimation.
78
79
  */
79
80
  function estimateTokens(messages) {
80
- let chars = 0;
81
+ let combined = "";
81
82
  for (const msg of messages) {
82
83
  for (const block of msg.blocks) {
83
84
  if ("content" in block && typeof block.content === "string") {
84
- chars += block.content.length;
85
+ combined += block.content;
85
86
  }
86
87
  if (block.type === "tool" &&
87
88
  block.parameters &&
88
89
  typeof block.parameters === "string") {
89
- chars += block.parameters.length;
90
+ combined += block.parameters;
90
91
  }
91
92
  if (block.type === "tool" && block.result) {
92
- chars += block.result.length;
93
+ combined += block.result;
93
94
  }
94
95
  }
95
96
  }
96
- return Math.ceil(chars / 4);
97
+ return estimateStrTokens(combined);
97
98
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * CJK-aware token estimation without external dependencies.
3
+ *
4
+ * The naive `length / 4` heuristic under-estimates CJK text by ~4x because
5
+ * each Chinese/Japanese/Korean character is typically 1-2 tokens, not 0.25.
6
+ * This function separates CJK characters from other text and applies
7
+ * different ratios:
8
+ *
9
+ * - CJK characters: 1 char ≈ 1 token
10
+ * - Other characters: 4 chars ≈ 1 token (2 for JSON/JSONL/JSONC)
11
+ *
12
+ * The estimate intentionally leans high for CJK (safe direction for limit
13
+ * checks — better to reject than to let oversized content through).
14
+ */
15
+ /**
16
+ * Estimate token count for a string, with CJK-awareness.
17
+ *
18
+ * @param content - The text content to estimate
19
+ * @param ext - File extension (without dot), e.g. "ts", "json". JSON/JSONL/JSONC
20
+ * files use a tighter ratio (2 chars/token) for non-CJK text.
21
+ * @returns Estimated token count
22
+ */
23
+ export declare function estimateTokens(content: string, ext?: string): number;
24
+ //# sourceMappingURL=tokenEstimate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenEstimate.d.ts","sourceRoot":"","sources":["../../src/utils/tokenEstimate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAMpE"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * CJK-aware token estimation without external dependencies.
3
+ *
4
+ * The naive `length / 4` heuristic under-estimates CJK text by ~4x because
5
+ * each Chinese/Japanese/Korean character is typically 1-2 tokens, not 0.25.
6
+ * This function separates CJK characters from other text and applies
7
+ * different ratios:
8
+ *
9
+ * - CJK characters: 1 char ≈ 1 token
10
+ * - Other characters: 4 chars ≈ 1 token (2 for JSON/JSONL/JSONC)
11
+ *
12
+ * The estimate intentionally leans high for CJK (safe direction for limit
13
+ * checks — better to reject than to let oversized content through).
14
+ */
15
+ // CJK Unified Ideographs + Extension A + Hiragana + Katakana + Hangul Syllables
16
+ const CJK_REGEX = /[\u4e00-\u9fff\u3400-\u4dbf\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g;
17
+ /**
18
+ * Estimate token count for a string, with CJK-awareness.
19
+ *
20
+ * @param content - The text content to estimate
21
+ * @param ext - File extension (without dot), e.g. "ts", "json". JSON/JSONL/JSONC
22
+ * files use a tighter ratio (2 chars/token) for non-CJK text.
23
+ * @returns Estimated token count
24
+ */
25
+ export function estimateTokens(content, ext) {
26
+ const cjkCount = (content.match(CJK_REGEX) || []).length;
27
+ const otherCount = content.length - cjkCount;
28
+ const bytesPerToken = ext === "json" || ext === "jsonl" || ext === "jsonc" ? 2 : 4;
29
+ return Math.ceil(cjkCount + otherCount / bytesPerToken);
30
+ }
@@ -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,CA6JtE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAmEvD;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,CAsClD"}
1
+ {"version":3,"file":"worktreeUtils.d.ts","sourceRoot":"","sources":["../../src/utils/worktreeUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,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,CAgKtE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAmEvD;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,CAsClD"}
@@ -5,7 +5,7 @@
5
5
  import { execFileSync } from "node:child_process";
6
6
  import * as path from "node:path";
7
7
  import * as fs from "node:fs";
8
- import { getGitMainRepoRoot, getDefaultRemoteBranch } from "./gitUtils.js";
8
+ import { getGitMainRepoRoot, getDefaultRemoteBranch, ensureWaveRuntimeFilesExcluded, } from "./gitUtils.js";
9
9
  import { logger } from "./globalLogger.js";
10
10
  /**
11
11
  * Validate a worktree name to prevent path traversal and invalid characters.
@@ -79,6 +79,8 @@ export function createWorktree(name, cwd) {
79
79
  const worktreePath = path.join(repoRoot, ".wave", "worktrees", name);
80
80
  const branchName = `worktree-${name}`;
81
81
  const baseBranch = getDefaultRemoteBranch(cwd);
82
+ // Ensure Wave runtime files are git-excluded in this repo
83
+ ensureWaveRuntimeFilesExcluded(cwd);
82
84
  // Ensure parent directory exists
83
85
  const parentDir = path.dirname(worktreePath);
84
86
  if (!fs.existsSync(parentDir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wave-agent-sdk",
3
- "version": "0.18.6",
3
+ "version": "0.18.7",
4
4
  "description": "SDK for building AI-powered development tools and agents",
5
5
  "keywords": [
6
6
  "ai",
@@ -3,6 +3,7 @@ import * as aiService from "../services/aiService.js";
3
3
  import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
4
4
  import { parseTaskNotificationXml } from "../utils/notificationXml.js";
5
5
  import { calculateComprehensiveTotalTokens } from "../utils/tokenCalculation.js";
6
+ import { estimateTokens } from "../utils/tokenEstimate.js";
6
7
  import {
7
8
  getTaskReminderTurnCounts,
8
9
  maybeInjectTaskReminder,
@@ -606,7 +607,7 @@ export class AIManager {
606
607
  );
607
608
  let usedTokens = 0;
608
609
  for (const file of recentFiles) {
609
- const fileTokens = Math.ceil(file.content.length / 4);
610
+ const fileTokens = estimateTokens(file.content);
610
611
  if (usedTokens + fileTokens > POST_COMPACT_MAX_TOKENS_PER_FILE) continue;
611
612
  if (fileTokens > 0) usedTokens += fileTokens;
612
613
  contextParts.push(`\n\n## ${file.path}\n\`\`\`\n${file.content}\n\`\`\``);
@@ -639,7 +640,7 @@ export class AIManager {
639
640
  skillContent.slice(0, maxSkillChars) + "\n\n...[truncated]...";
640
641
  }
641
642
 
642
- const skillTokens = Math.ceil(skillContent.length / 4);
643
+ const skillTokens = estimateTokens(skillContent);
643
644
  if (skillsUsedTokens + skillTokens > POST_COMPACT_SKILLS_TOKEN_BUDGET)
644
645
  break;
645
646
  skillsUsedTokens += skillTokens;
@@ -813,6 +814,7 @@ export class AIManager {
813
814
  let turnDepth = turnOffset;
814
815
 
815
816
  inner: while (true) {
817
+ let llmSpan: import("@opentelemetry/api").Span | undefined;
816
818
  try {
817
819
  // Save session in each iteration to ensure message persistence
818
820
  await this.messageManager.saveSession();
@@ -949,15 +951,21 @@ export class AIManager {
949
951
  };
950
952
  }
951
953
 
952
- startLLMRequestSpan(model || this.getModelConfig().model || "");
954
+ llmSpan = startLLMRequestSpan(
955
+ model || this.getModelConfig().model || "",
956
+ );
953
957
 
954
958
  const result = await aiService.callAgent(callAgentOptions);
955
959
 
956
960
  // End LLM span with usage data
957
- endLLMRequestSpan({
961
+ endLLMRequestSpan(llmSpan, {
958
962
  model: model || this.getModelConfig().model || "",
959
963
  success: true,
960
964
  hasToolCall: !!(result.tool_calls && result.tool_calls.length > 0),
965
+ inputTokens: result.usage?.prompt_tokens,
966
+ outputTokens: result.usage?.completion_tokens,
967
+ cacheReadTokens: result.usage?.cache_read_input_tokens,
968
+ cacheCreationTokens: result.usage?.cache_creation_input_tokens,
961
969
  });
962
970
 
963
971
  const createdByStreaming = assistantMessageCreated;
@@ -1238,7 +1246,7 @@ export class AIManager {
1238
1246
  break inner;
1239
1247
  } catch (error) {
1240
1248
  // End LLM span with error
1241
- endLLMRequestSpan({
1249
+ endLLMRequestSpan(llmSpan, {
1242
1250
  model: model || this.getModelConfig().model || "",
1243
1251
  success: false,
1244
1252
  error: error instanceof Error ? error.message : String(error),
@@ -1653,6 +1661,7 @@ export class AIManager {
1653
1661
  shortResult: toolResult.shortResult,
1654
1662
  isManuallyBackgrounded: toolResult.isManuallyBackgrounded,
1655
1663
  startLineNumber: toolResult.startLineNumber,
1664
+ images: toolResult.images,
1656
1665
  timestamp: Date.now(),
1657
1666
  });
1658
1667
 
@@ -15,6 +15,7 @@ import {
15
15
  releaseSchedulerLock,
16
16
  registerSchedulerLockCleanup,
17
17
  } from "../utils/cronTasksLock.js";
18
+ import { ensureWaveRuntimeFilesExcluded } from "../utils/gitUtils.js";
18
19
 
19
20
  export class CronManager {
20
21
  private jobs = new Map<string, CronJob>();
@@ -100,6 +101,7 @@ export class CronManager {
100
101
  }
101
102
 
102
103
  private tryLock(workdir: string): void {
104
+ ensureWaveRuntimeFilesExcluded(workdir);
103
105
  tryAcquireSchedulerLock({ dir: workdir, sessionId: this.sessionId })
104
106
  .then((acquired) => {
105
107
  if (acquired) {
@@ -17,7 +17,6 @@ import type { HookManager } from "./hookManager.js";
17
17
  import type { PermissionManager } from "./permissionManager.js";
18
18
  import { isValidHookEvent } from "../types/hooks.js";
19
19
  import { ConfigurationService } from "../services/configurationService.js";
20
- import { ensureGlobalGitIgnore } from "../utils/fileUtils.js";
21
20
  import { Container } from "../utils/container.js";
22
21
 
23
22
  import type {
@@ -97,9 +96,6 @@ export class LiveConfigManager {
97
96
  if (projectPaths) {
98
97
  for (const projectPath of projectPaths) {
99
98
  if (existsSync(projectPath)) {
100
- if (projectPath.endsWith("settings.local.json")) {
101
- await ensureGlobalGitIgnore("**/.wave/settings.local.json");
102
- }
103
99
  await this.fileWatcher.watchFile(projectPath, (event) =>
104
100
  this.handleFileChange(event, "project"),
105
101
  );
@@ -364,14 +360,6 @@ export class LiveConfigManager {
364
360
 
365
361
  // Handle file creation or modification
366
362
  if (event.type === "change" || event.type === "create") {
367
- if (
368
- source === "project" &&
369
- event.path.endsWith("settings.local.json") &&
370
- event.type === "create"
371
- ) {
372
- await ensureGlobalGitIgnore("**/.wave/settings.local.json");
373
- }
374
-
375
363
  // Add small delay to ensure file write is complete
376
364
  await new Promise((resolve) => setTimeout(resolve, 50));
377
365
 
@@ -848,9 +848,16 @@ export class McpManager {
848
848
  textContent.push(String(result.content));
849
849
  }
850
850
 
851
+ const textContentStr =
852
+ textContent.length > 0
853
+ ? textContent.join("\n")
854
+ : images.length > 0
855
+ ? `Tool returned ${images.length} image(s).`
856
+ : "No content";
857
+
851
858
  return {
852
859
  success: true,
853
- content: textContent.length > 0 ? textContent.join("\n") : "No content",
860
+ content: textContentStr,
854
861
  images: images.length > 0 ? images : undefined,
855
862
  serverName,
856
863
  };
@@ -31,6 +31,7 @@ import type { MemoryRuleManager } from "./MemoryRuleManager.js";
31
31
  import type { MemoryRule } from "../types/memoryRule.js";
32
32
  import type { MemoryService } from "../services/memory.js";
33
33
  import { pathEncoder } from "../utils/pathEncoder.js";
34
+ import { estimateTokens } from "../utils/tokenEstimate.js";
34
35
  import { READ_TOOL_NAME } from "../constants/tools.js";
35
36
 
36
37
  import { Container } from "../utils/container.js";
@@ -1063,7 +1064,7 @@ export class MessageManager {
1063
1064
  /**
1064
1065
  * Get recent file read contents, sorted by timestamp (newest first).
1065
1066
  * @param maxFiles - Maximum number of files to return
1066
- * @param maxTokensPerFile - Maximum tokens per file (~4 chars/token)
1067
+ * @param maxTokensPerFile - Maximum tokens per file (CJK-aware estimation)
1067
1068
  * @returns Array of { path, content } sorted by recency
1068
1069
  */
1069
1070
  public getRecentFileReads(
@@ -1076,10 +1077,14 @@ export class MessageManager {
1076
1077
 
1077
1078
  const result: Array<{ path: string; content: string }> = [];
1078
1079
  for (const [path, { content }] of sorted) {
1079
- const truncated =
1080
- content.length > maxTokensPerFile * 4
1081
- ? content.slice(0, maxTokensPerFile * 4)
1082
- : content;
1080
+ const tokenCount = estimateTokens(content);
1081
+ let truncated = content;
1082
+ if (tokenCount > maxTokensPerFile) {
1083
+ // Truncate proportionally to fit within the token budget
1084
+ const ratio = maxTokensPerFile / tokenCount;
1085
+ const cutIndex = Math.floor(content.length * ratio);
1086
+ truncated = content.slice(0, cutIndex);
1087
+ }
1083
1088
  result.push({ path, content: truncated });
1084
1089
  }
1085
1090
  return result;
@@ -279,6 +279,7 @@ class ToolManager {
279
279
  endToolSpan({
280
280
  success: result.success,
281
281
  durationMs: Date.now() - toolStartTime,
282
+ output: result.content,
282
283
  });
283
284
  return result;
284
285
  } catch (error) {
@@ -303,6 +304,7 @@ class ToolManager {
303
304
  endToolSpan({
304
305
  success: result.success,
305
306
  durationMs: Date.now() - toolStartTime,
307
+ output: result.content,
306
308
  });
307
309
  return result;
308
310
  } catch (error) {
@@ -47,6 +47,7 @@ import {
47
47
  mergeRemoteSettings,
48
48
  } from "./remoteSettingsService.js";
49
49
  import { createAuthAwareFetch } from "./authService.js";
50
+ import { ensureWaveRuntimeFilesExcluded } from "../utils/gitUtils.js";
50
51
 
51
52
  /**
52
53
  * Default ConfigurationService implementation
@@ -791,6 +792,7 @@ export class ConfigurationService {
791
792
 
792
793
  // Ensure .wave directory exists
793
794
  const waveDir = path.join(workdir, ".wave");
795
+ ensureWaveRuntimeFilesExcluded(workdir);
794
796
  if (!existsSync(waveDir)) {
795
797
  await fs.mkdir(waveDir, { recursive: true });
796
798
  }