wave-agent-sdk 0.18.6 → 0.19.0

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 (129) hide show
  1. package/builtin/skills/settings/ENV.md +0 -1
  2. package/builtin/skills/settings/HOOKS.md +10 -0
  3. package/builtin/skills/settings/MODELS.md +37 -0
  4. package/builtin/skills/settings/SKILLS.md +6 -4
  5. package/dist/agent.d.ts.map +1 -1
  6. package/dist/agent.js +12 -5
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/managers/aiManager.d.ts.map +1 -1
  11. package/dist/managers/aiManager.js +31 -8
  12. package/dist/managers/cronManager.d.ts.map +1 -1
  13. package/dist/managers/cronManager.js +2 -0
  14. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  15. package/dist/managers/liveConfigManager.js +0 -9
  16. package/dist/managers/mcpManager.d.ts.map +1 -1
  17. package/dist/managers/mcpManager.js +13 -3
  18. package/dist/managers/messageManager.d.ts +4 -6
  19. package/dist/managers/messageManager.d.ts.map +1 -1
  20. package/dist/managers/messageManager.js +27 -81
  21. package/dist/managers/skillManager.d.ts +1 -0
  22. package/dist/managers/skillManager.d.ts.map +1 -1
  23. package/dist/managers/skillManager.js +11 -2
  24. package/dist/managers/toolManager.d.ts.map +1 -1
  25. package/dist/managers/toolManager.js +2 -0
  26. package/dist/prompts/index.d.ts +3 -2
  27. package/dist/prompts/index.d.ts.map +1 -1
  28. package/dist/prompts/index.js +5 -1
  29. package/dist/services/aiService.d.ts.map +1 -1
  30. package/dist/services/aiService.js +13 -9
  31. package/dist/services/configurationService.d.ts.map +1 -1
  32. package/dist/services/configurationService.js +24 -9
  33. package/dist/services/hook.d.ts.map +1 -1
  34. package/dist/services/hook.js +6 -0
  35. package/dist/services/initializationService.d.ts.map +1 -1
  36. package/dist/services/initializationService.js +1 -1
  37. package/dist/services/interactionService.js +1 -1
  38. package/dist/services/reversionService.d.ts +5 -1
  39. package/dist/services/reversionService.d.ts.map +1 -1
  40. package/dist/services/reversionService.js +50 -22
  41. package/dist/services/session.d.ts +4 -13
  42. package/dist/services/session.d.ts.map +1 -1
  43. package/dist/services/session.js +59 -215
  44. package/dist/telemetry/sessionTracing.d.ts +31 -20
  45. package/dist/telemetry/sessionTracing.d.ts.map +1 -1
  46. package/dist/telemetry/sessionTracing.js +192 -62
  47. package/dist/tools/bashTool.d.ts.map +1 -1
  48. package/dist/tools/bashTool.js +16 -1
  49. package/dist/tools/readTool.d.ts.map +1 -1
  50. package/dist/tools/readTool.js +6 -7
  51. package/dist/types/config.d.ts +7 -0
  52. package/dist/types/config.d.ts.map +1 -1
  53. package/dist/types/hooks.d.ts +1 -0
  54. package/dist/types/hooks.d.ts.map +1 -1
  55. package/dist/types/messaging.d.ts +0 -1
  56. package/dist/types/messaging.d.ts.map +1 -1
  57. package/dist/types/skills.d.ts +1 -0
  58. package/dist/types/skills.d.ts.map +1 -1
  59. package/dist/types/telemetry.d.ts +16 -1
  60. package/dist/types/telemetry.d.ts.map +1 -1
  61. package/dist/utils/atomicWrite.d.ts +15 -0
  62. package/dist/utils/atomicWrite.d.ts.map +1 -0
  63. package/dist/utils/atomicWrite.js +59 -0
  64. package/dist/utils/cacheControlUtils.d.ts +3 -15
  65. package/dist/utils/cacheControlUtils.d.ts.map +1 -1
  66. package/dist/utils/cacheControlUtils.js +4 -35
  67. package/dist/utils/containerSetup.d.ts.map +1 -1
  68. package/dist/utils/containerSetup.js +5 -1
  69. package/dist/utils/convertMessagesForAPI.d.ts +9 -1
  70. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  71. package/dist/utils/convertMessagesForAPI.js +74 -55
  72. package/dist/utils/fileUtils.d.ts +0 -6
  73. package/dist/utils/fileUtils.d.ts.map +1 -1
  74. package/dist/utils/fileUtils.js +0 -43
  75. package/dist/utils/gitUtils.d.ts +11 -0
  76. package/dist/utils/gitUtils.d.ts.map +1 -1
  77. package/dist/utils/gitUtils.js +51 -0
  78. package/dist/utils/groupMessagesByApiRound.d.ts.map +1 -1
  79. package/dist/utils/groupMessagesByApiRound.js +7 -6
  80. package/dist/utils/mcpUtils.d.ts.map +1 -1
  81. package/dist/utils/mcpUtils.js +2 -1
  82. package/dist/utils/modelCapabilities.d.ts +22 -0
  83. package/dist/utils/modelCapabilities.d.ts.map +1 -0
  84. package/dist/utils/modelCapabilities.js +24 -0
  85. package/dist/utils/skillParser.d.ts.map +1 -1
  86. package/dist/utils/skillParser.js +3 -1
  87. package/dist/utils/tokenEstimate.d.ts +24 -0
  88. package/dist/utils/tokenEstimate.d.ts.map +1 -0
  89. package/dist/utils/tokenEstimate.js +30 -0
  90. package/dist/utils/worktreeUtils.d.ts.map +1 -1
  91. package/dist/utils/worktreeUtils.js +3 -1
  92. package/package.json +1 -1
  93. package/src/agent.ts +11 -5
  94. package/src/index.ts +1 -0
  95. package/src/managers/aiManager.ts +36 -7
  96. package/src/managers/cronManager.ts +2 -0
  97. package/src/managers/liveConfigManager.ts +0 -12
  98. package/src/managers/mcpManager.ts +16 -3
  99. package/src/managers/messageManager.ts +32 -109
  100. package/src/managers/skillManager.ts +16 -2
  101. package/src/managers/toolManager.ts +2 -0
  102. package/src/prompts/index.ts +6 -1
  103. package/src/services/aiService.ts +12 -5
  104. package/src/services/configurationService.ts +26 -24
  105. package/src/services/hook.ts +7 -0
  106. package/src/services/initializationService.ts +1 -3
  107. package/src/services/interactionService.ts +1 -1
  108. package/src/services/reversionService.ts +50 -27
  109. package/src/services/session.ts +63 -262
  110. package/src/telemetry/sessionTracing.ts +225 -73
  111. package/src/tools/bashTool.ts +23 -1
  112. package/src/tools/readTool.ts +6 -12
  113. package/src/types/config.ts +8 -0
  114. package/src/types/hooks.ts +1 -0
  115. package/src/types/messaging.ts +0 -1
  116. package/src/types/skills.ts +1 -0
  117. package/src/types/telemetry.ts +19 -1
  118. package/src/utils/atomicWrite.ts +61 -0
  119. package/src/utils/cacheControlUtils.ts +5 -38
  120. package/src/utils/containerSetup.ts +5 -1
  121. package/src/utils/convertMessagesForAPI.ts +87 -57
  122. package/src/utils/fileUtils.ts +0 -46
  123. package/src/utils/gitUtils.ts +54 -0
  124. package/src/utils/groupMessagesByApiRound.ts +7 -6
  125. package/src/utils/mcpUtils.ts +2 -1
  126. package/src/utils/modelCapabilities.ts +30 -0
  127. package/src/utils/skillParser.ts +3 -1
  128. package/src/utils/tokenEstimate.ts +34 -0
  129. package/src/utils/worktreeUtils.ts +8 -1
@@ -1 +1 @@
1
- {"version":3,"file":"initializationService.d.ts","sourceRoot":"","sources":["../../src/services/initializationService.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,OAAO,EACP,MAAM,EACN,YAAY,EACZ,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,eAAe,CAAC;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,YAAY,CAAC;IACtB,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,WAAW,CAAC;IACxB,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,WAAW,EAAE,WAAW,CAAC;IACzB,wBAAwB,EAAE,MAAM,IAAI,CAAC;CACtC;AAED,qBAAa,qBAAqB;WACZ,UAAU,CAC5B,OAAO,EAAE,qBAAqB,EAC9B,OAAO,CAAC,EAAE;QACR,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAC9B,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;KACtB,GACA,OAAO,CAAC,IAAI,CAAC;CAkSjB"}
1
+ {"version":3,"file":"initializationService.d.ts","sourceRoot":"","sources":["../../src/services/initializationService.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,OAAO,EACP,MAAM,EACN,YAAY,EACZ,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,eAAe,CAAC;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,YAAY,CAAC;IACtB,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,WAAW,CAAC;IACxB,oBAAoB,EAAE,oBAAoB,CAAC;IAC3C,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,WAAW,EAAE,WAAW,CAAC;IACzB,wBAAwB,EAAE,MAAM,IAAI,CAAC;CACtC;AAED,qBAAa,qBAAqB;WACZ,UAAU,CAC5B,OAAO,EAAE,qBAAqB,EAC9B,OAAO,CAAC,EAAE;QACR,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAC9B,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;KACtB,GACA,OAAO,CAAC,IAAI,CAAC;CAgSjB"}
@@ -192,7 +192,7 @@ export class InitializationService {
192
192
  if (sessionToRestore) {
193
193
  messageManager.initializeFromSession(sessionToRestore);
194
194
  // Update task manager with the root session ID to ensure continuity across compactions
195
- taskManager.setTaskListId(sessionToRestore.rootSessionId || sessionToRestore.id);
195
+ taskManager.setTaskListId(sessionToRestore.id);
196
196
  // After session is initialized, load tasks for the session
197
197
  const tasks = await taskManager.listTasks();
198
198
  agentOptions.callbacks?.onTasksChange?.(tasks);
@@ -92,7 +92,7 @@ export class InteractionService {
92
92
  // 6. Initialize session state last
93
93
  messageManager.initializeFromSession(sessionData);
94
94
  // Update task manager with the root session ID to ensure continuity across compactions
95
- taskManager.setTaskListId(sessionData.rootSessionId || sessionData.id);
95
+ taskManager.setTaskListId(sessionData.id);
96
96
  // 7. Load tasks for the restored session
97
97
  const tasks = await taskManager.listTasks();
98
98
  options.callbacks?.onTasksChange?.(tasks);
@@ -5,7 +5,6 @@ export declare class ReversionService {
5
5
  constructor(rootSessionId: string);
6
6
  private getFilePathHash;
7
7
  private getNextVersion;
8
- private updateVersionsFile;
9
8
  /**
10
9
  * Saves a single snapshot to the file history directory.
11
10
  * Returns the snapshot path.
@@ -19,6 +18,11 @@ export declare class ReversionService {
19
18
  * Deletes all snapshots for this session.
20
19
  */
21
20
  deleteSessionHistory(): Promise<void>;
21
+ /**
22
+ * Cleans up session directories older than the given number of days.
23
+ * Skips the current session.
24
+ */
25
+ cleanupOldSessions(days?: number): Promise<void>;
22
26
  private ensureDirectory;
23
27
  }
24
28
  //# sourceMappingURL=reversionService.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reversionService.d.ts","sourceRoot":"","sources":["../../src/services/reversionService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;gBAElB,aAAa,EAAE,MAAM;IAUjC,OAAO,CAAC,eAAe;YAIT,cAAc;YAad,kBAAkB;IAOhC;;;OAGG;IACG,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAuB3D;;OAEG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAWvE;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;YAI7B,eAAe;CAG9B"}
1
+ {"version":3,"file":"reversionService.d.ts","sourceRoot":"","sources":["../../src/services/reversionService.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;gBAElB,aAAa,EAAE,MAAM;IAUjC,OAAO,CAAC,eAAe;YAIT,cAAc;IAoB5B;;;OAGG;IACG,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB3D;;OAEG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAWvE;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3C;;;OAGG;IACG,kBAAkB,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,IAAI,CAAC;YAwB5C,eAAe;CAG9B"}
@@ -1,41 +1,44 @@
1
- import { readFile, writeFile, mkdir, rm } from "fs/promises";
2
- import { join } from "path";
1
+ import { readFile, writeFile, mkdir, rm, readdir, stat } from "fs/promises";
2
+ import { join, dirname } from "path";
3
3
  import { homedir } from "os";
4
4
  import { createHash } from "crypto";
5
5
  export class ReversionService {
6
6
  constructor(rootSessionId) {
7
- this.rootSessionId = rootSessionId;
8
- this.historyBaseDir = join(homedir(), ".wave", "file-history", rootSessionId || "default");
7
+ this.rootSessionId = rootSessionId || "default";
8
+ this.historyBaseDir = join(homedir(), ".wave", "file-history", this.rootSessionId);
9
9
  }
10
10
  getFilePathHash(filePath) {
11
- return createHash("md5").update(filePath).digest("hex");
11
+ return createHash("sha256").update(filePath).digest("hex").slice(0, 16);
12
12
  }
13
- async getNextVersion(fileHashDir) {
13
+ async getNextVersion(filePath) {
14
+ const hash = this.getFilePathHash(filePath);
15
+ const prefix = `${hash}@v`;
14
16
  try {
15
- const files = await readFile(join(fileHashDir, "versions"), "utf-8");
16
- const versions = files
17
- .split("\n")
18
- .map((v) => parseInt(v, 10))
19
- .filter((v) => !isNaN(v));
20
- return versions.length > 0 ? Math.max(...versions) + 1 : 1;
17
+ const files = await readdir(this.historyBaseDir);
18
+ let maxVersion = 0;
19
+ for (const file of files) {
20
+ if (file.startsWith(prefix)) {
21
+ const v = parseInt(file.slice(prefix.length), 10);
22
+ if (!isNaN(v) && v > maxVersion) {
23
+ maxVersion = v;
24
+ }
25
+ }
26
+ }
27
+ return maxVersion + 1;
21
28
  }
22
29
  catch {
23
30
  return 1;
24
31
  }
25
32
  }
26
- async updateVersionsFile(fileHashDir, version) {
27
- await appendFile(join(fileHashDir, "versions"), `${version}\n`, "utf-8");
28
- }
29
33
  /**
30
34
  * Saves a single snapshot to the file history directory.
31
35
  * Returns the snapshot path.
32
36
  */
33
37
  async saveSnapshot(snapshot) {
34
38
  const fileHash = this.getFilePathHash(snapshot.filePath);
35
- const fileHashDir = join(this.historyBaseDir, fileHash);
36
- await this.ensureDirectory(fileHashDir);
37
- const version = await this.getNextVersion(fileHashDir);
38
- const snapshotPath = join(fileHashDir, `v${version}`);
39
+ await this.ensureDirectory(this.historyBaseDir);
40
+ const version = await this.getNextVersion(snapshot.filePath);
41
+ const snapshotPath = join(this.historyBaseDir, `${fileHash}@v${version}`);
39
42
  const snapshotWithContent = snapshot;
40
43
  if (snapshotWithContent.content !== null) {
41
44
  await writeFile(snapshotPath, snapshotWithContent.content, "utf-8");
@@ -45,7 +48,6 @@ export class ReversionService {
45
48
  // The absence of the file at snapshotPath will indicate it should be deleted on reversion.
46
49
  return ""; // Return empty string to indicate no snapshot file
47
50
  }
48
- await this.updateVersionsFile(fileHashDir, version);
49
51
  return snapshotPath;
50
52
  }
51
53
  /**
@@ -68,9 +70,35 @@ export class ReversionService {
68
70
  async deleteSessionHistory() {
69
71
  await rm(this.historyBaseDir, { recursive: true, force: true });
70
72
  }
73
+ /**
74
+ * Cleans up session directories older than the given number of days.
75
+ * Skips the current session.
76
+ */
77
+ async cleanupOldSessions(days = 30) {
78
+ const threshold = Date.now() - days * 24 * 60 * 60 * 1000;
79
+ const parentDir = dirname(this.historyBaseDir);
80
+ try {
81
+ const entries = await readdir(parentDir);
82
+ await Promise.all(entries.map(async (entry) => {
83
+ if (entry === this.rootSessionId)
84
+ return;
85
+ const dirPath = join(parentDir, entry);
86
+ try {
87
+ const stats = await stat(dirPath);
88
+ if (stats.isDirectory() && stats.mtimeMs < threshold) {
89
+ await rm(dirPath, { recursive: true, force: true });
90
+ }
91
+ }
92
+ catch {
93
+ // ENOENT — concurrently deleted, skip
94
+ }
95
+ }));
96
+ }
97
+ catch {
98
+ // parent dir doesn't exist — nothing to clean
99
+ }
100
+ }
71
101
  async ensureDirectory(dirPath) {
72
102
  await mkdir(dirPath, { recursive: true });
73
103
  }
74
104
  }
75
- // Helper to avoid appendFile import error if not imported
76
- import { appendFile } from "fs/promises";
@@ -17,8 +17,6 @@
17
17
  import type { Message } from "../types/index.js";
18
18
  export interface SessionData {
19
19
  id: string;
20
- rootSessionId?: string;
21
- parentSessionId?: string;
22
20
  messages: Message[];
23
21
  metadata: {
24
22
  workdir: string;
@@ -28,8 +26,6 @@ export interface SessionData {
28
26
  }
29
27
  export interface SessionMetadata {
30
28
  id: string;
31
- rootSessionId?: string;
32
- parentSessionId?: string;
33
29
  sessionType: "main" | "subagent";
34
30
  subagentType?: string;
35
31
  workdir: string;
@@ -38,13 +34,6 @@ export interface SessionMetadata {
38
34
  latestTotalTokens: number;
39
35
  firstMessage?: string;
40
36
  }
41
- export interface SessionIndex {
42
- sessions: Record<string, Omit<SessionMetadata, "id" | "lastActiveAt" | "createdAt"> & {
43
- lastActiveAt: string;
44
- createdAt: string;
45
- }>;
46
- lastUpdated: string;
47
- }
48
37
  /**
49
38
  * Generate a new session ID using Node.js native crypto.randomUUID()
50
39
  * @returns UUID string for session identification
@@ -92,7 +81,7 @@ export declare function createSession(sessionId: string, workdir: string, sessio
92
81
  * @param workdir - Working directory for the session
93
82
  * @param sessionType - Type of session ("main" or "subagent", defaults to "main")
94
83
  */
95
- export declare function appendMessages(sessionId: string, newMessages: Message[], workdir: string, sessionType?: "main" | "subagent", rootSessionId?: string, parentSessionId?: string): Promise<void>;
84
+ export declare function appendMessages(sessionId: string, newMessages: Message[], workdir: string, sessionType?: "main" | "subagent"): Promise<void>;
96
85
  /**
97
86
  * Load session data from JSONL file (new approach)
98
87
  *
@@ -193,7 +182,9 @@ export declare function truncateContent(content: string, maxLength?: number): st
193
182
  */
194
183
  export declare function handleSessionRestoration(restoreSessionId?: string, continueLastSession?: boolean, workdir?: string): Promise<SessionData | undefined>;
195
184
  /**
196
- * Load the full message thread by following parentSessionId links
185
+ * Load the full message thread for a session.
186
+ * With append-only compaction, all messages are in a single file.
187
+ * Returns the active messages (post-compact boundary).
197
188
  * @param currentSessionId - The ID of the current session
198
189
  * @param workdir - Working directory for the session
199
190
  * @returns Promise that resolves to an array of all messages in the thread
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/services/session.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/services/session.ts"],"names":[],"mappings":"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"}
@@ -41,34 +41,6 @@ export function generateSubagentFilename(sessionId) {
41
41
  // Constants
42
42
  export const SESSION_DIR = join(homedir(), ".wave", "projects");
43
43
  const MAX_SESSION_AGE_DAYS = 14;
44
- const SESSION_INDEX_FILENAME = "sessions-index.json";
45
- /**
46
- * Update the session index for a project directory
47
- */
48
- async function updateSessionIndex(projectDirPath, metadata) {
49
- const indexPath = join(projectDirPath, SESSION_INDEX_FILENAME);
50
- let index = {
51
- sessions: {},
52
- lastUpdated: new Date().toISOString(),
53
- };
54
- try {
55
- const content = await fs.readFile(indexPath, "utf8");
56
- index = JSON.parse(content);
57
- }
58
- catch {
59
- // Index doesn't exist or is invalid, start fresh
60
- }
61
- const { id, ...rest } = metadata;
62
- index.sessions[id] = {
63
- ...rest,
64
- createdAt: metadata.createdAt.toISOString(),
65
- lastActiveAt: metadata.lastActiveAt.toISOString(),
66
- firstMessage: metadata.firstMessage || index.sessions[id]?.firstMessage,
67
- parentSessionId: metadata.parentSessionId,
68
- };
69
- index.lastUpdated = new Date().toISOString();
70
- await fs.writeFile(indexPath, JSON.stringify(index, null, 2), "utf8");
71
- }
72
44
  /**
73
45
  * Ensure session directory exists
74
46
  */
@@ -129,7 +101,7 @@ export async function createSession(sessionId, workdir, sessionType = "main") {
129
101
  * @param workdir - Working directory for the session
130
102
  * @param sessionType - Type of session ("main" or "subagent", defaults to "main")
131
103
  */
132
- export async function appendMessages(sessionId, newMessages, workdir, sessionType = "main", rootSessionId, parentSessionId) {
104
+ export async function appendMessages(sessionId, newMessages, workdir, sessionType = "main") {
133
105
  // Do not save session files in test environment
134
106
  if (process.env.NODE_ENV === "test") {
135
107
  return;
@@ -151,47 +123,6 @@ export async function appendMessages(sessionId, newMessages, workdir, sessionTyp
151
123
  await jsonlHandler.append(filePath, newMessages, {
152
124
  atomic: false,
153
125
  });
154
- // Update index
155
- const encoder = new PathEncoder();
156
- const projectDir = await encoder.getProjectDirectory(workdir, SESSION_DIR);
157
- const lastMessage = newMessages[newMessages.length - 1];
158
- // Get first message content and createdAt from existing index
159
- let firstMessage;
160
- let createdAt;
161
- try {
162
- const indexPath = join(projectDir.encodedPath, SESSION_INDEX_FILENAME);
163
- const content = await fs.readFile(indexPath, "utf8");
164
- const index = JSON.parse(content);
165
- if (!index.sessions[sessionId]?.firstMessage) {
166
- firstMessage =
167
- (await getFirstMessageContent(sessionId, workdir)) || undefined;
168
- }
169
- if (index.sessions[sessionId]?.createdAt) {
170
- createdAt = new Date(index.sessions[sessionId].createdAt);
171
- }
172
- }
173
- catch {
174
- // If index doesn't exist, this might be the first message
175
- firstMessage =
176
- (await getFirstMessageContent(sessionId, workdir)) || undefined;
177
- }
178
- // Derive createdAt from session ID if not in index
179
- if (!createdAt) {
180
- createdAt = new Date();
181
- }
182
- await updateSessionIndex(projectDir.encodedPath, {
183
- id: sessionId,
184
- rootSessionId,
185
- parentSessionId,
186
- sessionType,
187
- workdir,
188
- createdAt,
189
- lastActiveAt: new Date(lastMessage.timestamp),
190
- latestTotalTokens: lastMessage.usage
191
- ? extractLatestTotalTokens([lastMessage])
192
- : 0,
193
- firstMessage,
194
- });
195
126
  }
196
127
  /**
197
128
  * Load session data from JSONL file (new approach)
@@ -216,28 +147,20 @@ export async function loadSessionFromJsonl(sessionId, workdir, sessionType = "ma
216
147
  }
217
148
  throw error;
218
149
  }
219
- const messages = await jsonlHandler.read(filePath);
150
+ const allMessages = await jsonlHandler.read(filePath);
151
+ // Find the last compact boundary — only return messages from there forward
152
+ let lastCompactIndex = -1;
153
+ for (let i = allMessages.length - 1; i >= 0; i--) {
154
+ if (allMessages[i].blocks?.some((b) => b.type === "compact")) {
155
+ lastCompactIndex = i;
156
+ break;
157
+ }
158
+ }
159
+ const messages = lastCompactIndex >= 0 ? allMessages.slice(lastCompactIndex) : allMessages;
220
160
  // Extract metadata from messages
221
161
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
222
- // Try to get rootSessionId and parentSessionId from index
223
- let rootSessionId;
224
- let parentSessionId;
225
- try {
226
- const encoder = new PathEncoder();
227
- const projectDir = await encoder.getProjectDirectory(workdir, SESSION_DIR);
228
- const indexPath = join(projectDir.encodedPath, SESSION_INDEX_FILENAME);
229
- const indexContent = await fs.readFile(indexPath, "utf8");
230
- const index = JSON.parse(indexContent);
231
- rootSessionId = index.sessions[sessionId]?.rootSessionId;
232
- parentSessionId = index.sessions[sessionId]?.parentSessionId;
233
- }
234
- catch {
235
- // Ignore index errors
236
- }
237
162
  const sessionData = {
238
163
  id: sessionId,
239
- rootSessionId: rootSessionId || sessionId,
240
- parentSessionId,
241
164
  messages,
242
165
  metadata: {
243
166
  workdir,
@@ -311,29 +234,8 @@ export async function listSessionsFromJsonl(workdir) {
311
234
  const encoder = new PathEncoder();
312
235
  const baseDir = SESSION_DIR;
313
236
  const projectDir = await encoder.getProjectDirectory(workdir, baseDir);
314
- // Try to read from index first
315
- const indexPath = join(projectDir.encodedPath, SESSION_INDEX_FILENAME);
316
237
  const sevenDaysAgo = new Date();
317
238
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
318
- try {
319
- const indexContent = await fs.readFile(indexPath, "utf8");
320
- const index = JSON.parse(indexContent);
321
- const sessions = Object.entries(index.sessions)
322
- .filter(([, meta]) => {
323
- const lastActiveAt = new Date(meta.lastActiveAt);
324
- return meta.sessionType === "main" && lastActiveAt >= sevenDaysAgo;
325
- })
326
- .map(([id, meta]) => ({
327
- id,
328
- ...meta,
329
- createdAt: new Date(meta.createdAt),
330
- lastActiveAt: new Date(meta.lastActiveAt),
331
- }));
332
- return sessions.sort((a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime());
333
- }
334
- catch {
335
- // Fallback to manual listing if index fails
336
- }
337
239
  let files;
338
240
  try {
339
241
  files = await fs.readdir(projectDir.encodedPath);
@@ -382,7 +284,7 @@ export async function listSessionsFromJsonl(workdir) {
382
284
  const sessionMeta = {
383
285
  id: sessionId,
384
286
  sessionType: "main",
385
- subagentType: undefined, // No longer stored in metadata
287
+ subagentType: undefined,
386
288
  workdir: projectDir.originalPath,
387
289
  createdAt: new Date(),
388
290
  lastActiveAt,
@@ -390,7 +292,7 @@ export async function listSessionsFromJsonl(workdir) {
390
292
  ? extractLatestTotalTokens([lastMessage])
391
293
  : 0,
392
294
  };
393
- // Try to get first message content for the fallback/rebuild case
295
+ // Try to get first message content for display
394
296
  try {
395
297
  const firstContent = await getFirstMessageContent(sessionId, workdir);
396
298
  if (firstContent) {
@@ -408,27 +310,7 @@ export async function listSessionsFromJsonl(workdir) {
408
310
  }
409
311
  }
410
312
  // Sort by last active time (most recently active first)
411
- const sortedSessions = sessions.sort((a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime());
412
- // Rebuild index if we had to fall back
413
- try {
414
- const index = {
415
- sessions: {},
416
- lastUpdated: new Date().toISOString(),
417
- };
418
- for (const session of sessions) {
419
- const { id, ...rest } = session;
420
- index.sessions[id] = {
421
- ...rest,
422
- createdAt: session.createdAt.toISOString(),
423
- lastActiveAt: session.lastActiveAt.toISOString(),
424
- };
425
- }
426
- await fs.writeFile(indexPath, JSON.stringify(index, null, 2), "utf8");
427
- }
428
- catch (error) {
429
- logger.warn(`Failed to rebuild session index for ${workdir}:`, error);
430
- }
431
- return sortedSessions;
313
+ return sessions.sort((a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime());
432
314
  }
433
315
  catch (error) {
434
316
  throw new Error(`Failed to list sessions: ${error}`);
@@ -462,30 +344,49 @@ export async function listAllSessions() {
462
344
  if (!stat.isDirectory()) {
463
345
  continue;
464
346
  }
465
- // Try to read from index first
466
- const indexPath = join(projectPath, SESSION_INDEX_FILENAME);
467
- try {
468
- const indexContent = await fs.readFile(indexPath, "utf8");
469
- const index = JSON.parse(indexContent);
470
- for (const [id, meta] of Object.entries(index.sessions)) {
471
- const lastActiveAt = new Date(meta.lastActiveAt);
472
- if (meta.sessionType === "main" && lastActiveAt >= sevenDaysAgo) {
473
- allSessions.push({
474
- id,
475
- ...meta,
476
- createdAt: new Date(meta.createdAt),
477
- lastActiveAt,
478
- });
347
+ // Scan .jsonl files in the project directory
348
+ const files = await fs.readdir(projectPath);
349
+ for (const file of files) {
350
+ if (!file.endsWith(".jsonl") || file.startsWith("subagent-")) {
351
+ continue;
352
+ }
353
+ try {
354
+ const filePath = join(projectPath, file);
355
+ const uuidMatch = file.match(/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/);
356
+ if (!uuidMatch)
357
+ continue;
358
+ const sessionId = uuidMatch[1];
359
+ const jsonlHandler = new JsonlHandler();
360
+ const lastMessage = await jsonlHandler.getLastMessage(filePath);
361
+ let lastActiveAt;
362
+ if (lastMessage) {
363
+ lastActiveAt = new Date(lastMessage.timestamp);
364
+ }
365
+ else {
366
+ const stats = await fs.stat(filePath);
367
+ lastActiveAt = stats.mtime;
479
368
  }
369
+ if (lastActiveAt < sevenDaysAgo)
370
+ continue;
371
+ allSessions.push({
372
+ id: sessionId,
373
+ sessionType: "main",
374
+ subagentType: undefined,
375
+ workdir: projectDirName,
376
+ createdAt: new Date(),
377
+ lastActiveAt,
378
+ latestTotalTokens: lastMessage?.usage
379
+ ? extractLatestTotalTokens([lastMessage])
380
+ : 0,
381
+ });
382
+ }
383
+ catch {
384
+ continue;
480
385
  }
481
- }
482
- catch {
483
- // If index fails, we skip this project directory
484
- // In the future, we could scan for .jsonl files here
485
386
  }
486
387
  }
487
388
  catch {
488
- // Skip if stat fails
389
+ // Skip if stat/readdir fails
489
390
  }
490
391
  }
491
392
  return allSessions.sort((a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime());
@@ -523,26 +424,6 @@ export async function cleanupExpiredSessionsFromJsonl(workdir) {
523
424
  if (fileAge > maxAge) {
524
425
  await fs.unlink(filePath);
525
426
  deletedCount++;
526
- // Remove from index if it exists
527
- try {
528
- const indexPath = join(projectDir.encodedPath, SESSION_INDEX_FILENAME);
529
- const indexContent = await fs.readFile(indexPath, "utf8");
530
- const index = JSON.parse(indexContent);
531
- const uuidMatch = file.match(/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/);
532
- const subagentMatch = file.match(/^subagent-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/);
533
- const sessionId = uuidMatch
534
- ? uuidMatch[1]
535
- : subagentMatch
536
- ? subagentMatch[1]
537
- : null;
538
- if (sessionId && index.sessions[sessionId]) {
539
- delete index.sessions[sessionId];
540
- await fs.writeFile(indexPath, JSON.stringify(index, null, 2), "utf8");
541
- }
542
- }
543
- catch {
544
- // Ignore index update errors during cleanup
545
- }
546
427
  }
547
428
  }
548
429
  catch {
@@ -697,21 +578,6 @@ export async function deleteSession(sessionId, workdir, sessionType = "main") {
697
578
  throw error;
698
579
  }
699
580
  }
700
- // Also remove from index
701
- try {
702
- const encoder = new PathEncoder();
703
- const projectDir = await encoder.getProjectDirectory(workdir, SESSION_DIR);
704
- const indexPath = join(projectDir.encodedPath, SESSION_INDEX_FILENAME);
705
- const indexContent = await fs.readFile(indexPath, "utf8");
706
- const index = JSON.parse(indexContent);
707
- if (index.sessions[sessionId]) {
708
- delete index.sessions[sessionId];
709
- await fs.writeFile(indexPath, JSON.stringify(index, null, 2), "utf8");
710
- }
711
- }
712
- catch {
713
- // Ignore index errors
714
- }
715
581
  }
716
582
  /**
717
583
  * Truncate content to a maximum length, adding ellipsis if truncated.
@@ -778,38 +644,16 @@ export async function handleSessionRestoration(restoreSessionId, continueLastSes
778
644
  }
779
645
  }
780
646
  /**
781
- * Load the full message thread by following parentSessionId links
647
+ * Load the full message thread for a session.
648
+ * With append-only compaction, all messages are in a single file.
649
+ * Returns the active messages (post-compact boundary).
782
650
  * @param currentSessionId - The ID of the current session
783
651
  * @param workdir - Working directory for the session
784
652
  * @returns Promise that resolves to an array of all messages in the thread
785
653
  */
786
654
  export async function loadFullMessageThread(currentSessionId, workdir) {
787
- const sessionIds = [];
788
- let currentId = currentSessionId;
789
- const allMessages = [];
790
- while (currentId) {
791
- const sessionData = await loadSessionFromJsonl(currentId, workdir);
792
- if (!sessionData)
793
- break;
794
- sessionIds.unshift(currentId);
795
- // Add messages from this session to the beginning of the list
796
- // But skip the "compact" block if it's not the first session in our traversal (which is the latest)
797
- // Actually, we should probably keep all messages and let the UI/logic handle it.
798
- // But wait, if we are concatenating, the "compact" block in session N summarizes session N-1.
799
- // So if we have session N-1 and session N, we should probably skip the compact block in session N.
800
- const messages = sessionData.messages;
801
- if (allMessages.length > 0) {
802
- // If we already have messages (from "later" sessions),
803
- // we are now adding messages from an "earlier" session.
804
- // The later session's first message might be a "compact" block.
805
- if (allMessages[0].blocks.some((b) => b.type === "compact")) {
806
- // Remove the compact block from the later session's messages
807
- // because we are now providing the actual messages it summarized.
808
- allMessages.shift();
809
- }
810
- }
811
- allMessages.unshift(...messages);
812
- currentId = sessionData.parentSessionId;
813
- }
814
- return { messages: allMessages, sessionIds };
655
+ const sessionData = await loadSessionFromJsonl(currentSessionId, workdir);
656
+ if (!sessionData)
657
+ return { messages: [], sessionIds: [] };
658
+ return { messages: sessionData.messages, sessionIds: [currentSessionId] };
815
659
  }