wave-agent-sdk 0.8.0 → 0.8.2

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 (54) hide show
  1. package/dist/managers/hookManager.d.ts.map +1 -1
  2. package/dist/managers/hookManager.js +0 -21
  3. package/dist/managers/liveConfigManager.d.ts.map +1 -1
  4. package/dist/managers/liveConfigManager.js +0 -36
  5. package/dist/managers/messageManager.d.ts.map +1 -1
  6. package/dist/managers/messageManager.js +2 -1
  7. package/dist/managers/skillManager.d.ts.map +1 -1
  8. package/dist/managers/skillManager.js +8 -2
  9. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  10. package/dist/managers/slashCommandManager.js +2 -14
  11. package/dist/services/aiService.d.ts.map +1 -1
  12. package/dist/services/aiService.js +5 -3
  13. package/dist/services/fileWatcher.d.ts.map +1 -1
  14. package/dist/services/fileWatcher.js +0 -4
  15. package/dist/services/initializationService.d.ts.map +1 -1
  16. package/dist/services/initializationService.js +2 -10
  17. package/dist/services/pluginLoader.d.ts.map +1 -1
  18. package/dist/services/pluginLoader.js +1 -3
  19. package/dist/services/taskManager.d.ts +2 -0
  20. package/dist/services/taskManager.d.ts.map +1 -1
  21. package/dist/services/taskManager.js +48 -0
  22. package/dist/tools/globTool.d.ts.map +1 -1
  23. package/dist/tools/globTool.js +12 -2
  24. package/dist/tools/taskManagementTools.d.ts.map +1 -1
  25. package/dist/tools/taskManagementTools.js +58 -0
  26. package/dist/tools/taskTool.d.ts.map +1 -1
  27. package/dist/tools/taskTool.js +60 -50
  28. package/dist/utils/commandArgumentParser.d.ts.map +1 -1
  29. package/dist/utils/commandArgumentParser.js +7 -0
  30. package/dist/utils/containerSetup.d.ts.map +1 -1
  31. package/dist/utils/containerSetup.js +3 -0
  32. package/dist/utils/messageOperations.d.ts +1 -0
  33. package/dist/utils/messageOperations.d.ts.map +1 -1
  34. package/dist/utils/messageOperations.js +6 -2
  35. package/dist/utils/openaiClient.d.ts.map +1 -1
  36. package/dist/utils/openaiClient.js +3 -1
  37. package/package.json +1 -1
  38. package/src/managers/hookManager.ts +0 -52
  39. package/src/managers/liveConfigManager.ts +0 -75
  40. package/src/managers/messageManager.ts +2 -0
  41. package/src/managers/skillManager.ts +8 -2
  42. package/src/managers/slashCommandManager.ts +8 -18
  43. package/src/services/aiService.ts +5 -3
  44. package/src/services/fileWatcher.ts +0 -8
  45. package/src/services/initializationService.ts +2 -19
  46. package/src/services/pluginLoader.ts +1 -3
  47. package/src/services/taskManager.ts +51 -0
  48. package/src/tools/globTool.ts +15 -2
  49. package/src/tools/taskManagementTools.ts +77 -0
  50. package/src/tools/taskTool.ts +70 -61
  51. package/src/utils/commandArgumentParser.ts +8 -0
  52. package/src/utils/containerSetup.ts +3 -0
  53. package/src/utils/messageOperations.ts +7 -2
  54. package/src/utils/openaiClient.ts +3 -1
@@ -83,8 +83,6 @@ export class LiveConfigManager {
83
83
  projectPaths?: string[],
84
84
  ): Promise<void> {
85
85
  try {
86
- logger?.debug("Live Config: Initializing configuration watching...");
87
-
88
86
  this.userConfigPaths = userPaths;
89
87
  this.projectConfigPaths = projectPaths;
90
88
 
@@ -94,16 +92,9 @@ export class LiveConfigManager {
94
92
  // Start watching user configs that exist
95
93
  for (const userPath of userPaths) {
96
94
  if (existsSync(userPath)) {
97
- logger?.debug(
98
- `Live Config: Starting to watch user config: ${userPath}`,
99
- );
100
95
  await this.fileWatcher.watchFile(userPath, (event) =>
101
96
  this.handleFileChange(event, "user"),
102
97
  );
103
- } else {
104
- logger?.debug(
105
- `Live Config: User config file does not exist: ${userPath}`,
106
- );
107
98
  }
108
99
  }
109
100
 
@@ -114,24 +105,14 @@ export class LiveConfigManager {
114
105
  if (projectPath.endsWith("settings.local.json")) {
115
106
  await ensureGlobalGitIgnore("**/.wave/settings.local.json");
116
107
  }
117
- logger?.debug(
118
- `Live Config: Starting to watch project config: ${projectPath}`,
119
- );
120
108
  await this.fileWatcher.watchFile(projectPath, (event) =>
121
109
  this.handleFileChange(event, "project"),
122
110
  );
123
- } else {
124
- logger?.debug(
125
- `Live Config: Project config file does not exist: ${projectPath}`,
126
- );
127
111
  }
128
112
  }
129
113
  }
130
114
 
131
115
  this.isWatching = true;
132
- logger?.debug(
133
- "Live Config: Configuration watching initialized successfully",
134
- );
135
116
  } catch (error) {
136
117
  const errorMessage = `Failed to initialize configuration watching: ${(error as Error).message}`;
137
118
  logger?.error(`Live Config: ${errorMessage}`);
@@ -151,7 +132,6 @@ export class LiveConfigManager {
151
132
  */
152
133
  async initialize(): Promise<void> {
153
134
  if (this.isInitialized) {
154
- logger?.debug("Already initialized");
155
135
  return;
156
136
  }
157
137
 
@@ -163,9 +143,6 @@ export class LiveConfigManager {
163
143
  await this.initializeWatching(userPaths, projectPaths);
164
144
 
165
145
  this.isInitialized = true;
166
- logger?.debug(
167
- "Live configuration management initialized with file watching",
168
- );
169
146
  } catch (error) {
170
147
  logger?.error(`Failed to initialize: ${(error as Error).message}`);
171
148
  throw error;
@@ -181,8 +158,6 @@ export class LiveConfigManager {
181
158
  }
182
159
 
183
160
  try {
184
- logger?.debug("Live Config: Shutting down configuration manager...");
185
-
186
161
  this.isWatching = false;
187
162
 
188
163
  // Cleanup file watcher
@@ -193,7 +168,6 @@ export class LiveConfigManager {
193
168
  this.lastValidConfiguration = null;
194
169
 
195
170
  this.isInitialized = false;
196
- logger?.debug("Live configuration management shutdown completed");
197
171
  } catch (error) {
198
172
  logger?.error(`Error during shutdown: ${(error as Error).message}`);
199
173
  throw error;
@@ -206,15 +180,12 @@ export class LiveConfigManager {
206
180
  */
207
181
  private async reloadConfiguration(): Promise<WaveConfiguration> {
208
182
  if (this.reloadInProgress) {
209
- logger?.debug("Live Config: Reload already in progress, skipping");
210
183
  return this.currentConfiguration || {};
211
184
  }
212
185
 
213
186
  this.reloadInProgress = true;
214
187
 
215
188
  try {
216
- logger?.debug("Live Config: Reloading configuration from files...");
217
-
218
189
  // Load merged configuration using ConfigurationService
219
190
  const loadResult: ConfigurationLoadResult =
220
191
  await this.configurationService.loadMergedConfiguration(this.workdir);
@@ -237,9 +208,6 @@ export class LiveConfigManager {
237
208
 
238
209
  // Use fallback configuration if available
239
210
  if (this.lastValidConfiguration) {
240
- logger?.debug(
241
- "Live Config: Using previous valid configuration due to loading errors",
242
- );
243
211
  this.currentConfiguration = this.lastValidConfiguration;
244
212
 
245
213
  // Apply environment variables to configuration service if configured
@@ -266,24 +234,6 @@ export class LiveConfigManager {
266
234
  }
267
235
  }
268
236
 
269
- // Log success with detailed information
270
- if (newConfig) {
271
- logger?.debug(
272
- `Live Config: Configuration loaded successfully from ${loadResult.sourcePath || "merged sources"}`,
273
- );
274
-
275
- // Log detailed configuration info
276
- const hookCount = Object.keys(newConfig.hooks || {}).length;
277
- const envCount = Object.keys(newConfig.env || {}).length;
278
- logger?.debug(
279
- `Live Config: Loaded ${hookCount} hook events and ${envCount} environment variables`,
280
- );
281
- } else {
282
- logger?.debug(
283
- "Live Config: No configuration found (using empty configuration)",
284
- );
285
- }
286
-
287
237
  // Log warnings from successful loading
288
238
  if (loadResult.warnings && loadResult.warnings.length > 0) {
289
239
  logger?.warn(
@@ -300,9 +250,6 @@ export class LiveConfigManager {
300
250
 
301
251
  // Use previous valid configuration for error recovery
302
252
  if (this.lastValidConfiguration) {
303
- logger?.debug(
304
- "Live Config: Using previous valid configuration due to validation errors",
305
- );
306
253
  this.currentConfiguration = this.lastValidConfiguration;
307
254
 
308
255
  // Apply environment variables to configuration service if configured
@@ -339,9 +286,6 @@ export class LiveConfigManager {
339
286
  // Save as last valid configuration if it's valid and not empty
340
287
  if (newConfig && (newConfig.hooks || newConfig.env)) {
341
288
  this.lastValidConfiguration = { ...newConfig };
342
- logger?.debug(
343
- "Live Config: Saved current configuration as last valid backup",
344
- );
345
289
  }
346
290
 
347
291
  // Note: Environment variables are already applied by loadMergedConfiguration()
@@ -378,10 +322,6 @@ export class LiveConfigManager {
378
322
  }
379
323
  }
380
324
 
381
- logger?.debug(
382
- `Live Config: Configuration reload completed successfully with ${Object.keys(newConfig?.hooks || {}).length} event types and ${Object.keys(newConfig?.env || {}).length} environment variables`,
383
- );
384
-
385
325
  return this.currentConfiguration;
386
326
  } catch (error) {
387
327
  const errorMessage = `Configuration reload failed with exception: ${(error as Error).message}`;
@@ -389,9 +329,6 @@ export class LiveConfigManager {
389
329
 
390
330
  // Use previous valid configuration for error recovery
391
331
  if (this.lastValidConfiguration) {
392
- logger?.debug(
393
- "Live Config: Using previous valid configuration due to reload exception",
394
- );
395
332
  this.currentConfiguration = this.lastValidConfiguration;
396
333
 
397
334
  // Apply environment variables to configuration service if configured
@@ -424,7 +361,6 @@ export class LiveConfigManager {
424
361
  * Reload configuration from files (public method)
425
362
  */
426
363
  async reload(): Promise<WaveConfiguration> {
427
- logger?.debug("Manually reloading configuration...");
428
364
  return await this.reloadConfiguration();
429
365
  }
430
366
 
@@ -464,16 +400,9 @@ export class LiveConfigManager {
464
400
  event: FileWatchEvent,
465
401
  source: Scope,
466
402
  ): Promise<void> {
467
- logger?.debug(
468
- `Live Config: File ${event.type} detected for ${source} config: ${event.path}`,
469
- );
470
-
471
403
  try {
472
404
  // Handle file deletion
473
405
  if (event.type === "delete") {
474
- logger?.debug(
475
- `Live Config: ${source} config file deleted: ${event.path}`,
476
- );
477
406
  // Reload configuration without the deleted file
478
407
  await this.reloadConfiguration();
479
408
  return;
@@ -481,10 +410,6 @@ export class LiveConfigManager {
481
410
 
482
411
  // Handle file creation or modification
483
412
  if (event.type === "change" || event.type === "create") {
484
- logger?.debug(
485
- `Live Config: ${source} config file ${event.type}: ${event.path}`,
486
- );
487
-
488
413
  if (
489
414
  source === "project" &&
490
415
  event.path.endsWith("settings.local.json") &&
@@ -9,6 +9,7 @@ import {
9
9
  removeLastUserMessage,
10
10
  UserMessageParams,
11
11
  type AgentToolBlockUpdateParams,
12
+ generateMessageId,
12
13
  } from "../utils/messageOperations.js";
13
14
  import type { Message, Usage, SlashCommand } from "../types/index.js";
14
15
  import { join } from "path";
@@ -422,6 +423,7 @@ export class MessageManager {
422
423
 
423
424
  // Create compressed message
424
425
  const compressMessage: Message = {
426
+ id: generateMessageId(),
425
427
  role: "assistant",
426
428
  blocks: [
427
429
  {
@@ -355,13 +355,19 @@ export class SkillManager {
355
355
  */
356
356
  registerPluginSkills(skills: Skill[]): void {
357
357
  for (const skill of skills) {
358
- this.skillMetadata.set(skill.name, {
358
+ const metadata: SkillMetadata = {
359
359
  name: skill.name,
360
360
  description: skill.description,
361
361
  type: skill.type,
362
362
  skillPath: skill.skillPath,
363
363
  allowedTools: skill.allowedTools,
364
- });
364
+ context: skill.context,
365
+ agent: skill.agent,
366
+ model: skill.model,
367
+ disableModelInvocation: skill.disableModelInvocation,
368
+ userInvocable: skill.userInvocable,
369
+ };
370
+ this.skillMetadata.set(skill.name, metadata);
365
371
  this.skillContent.set(skill.name, skill);
366
372
  }
367
373
  logger?.debug(
@@ -117,15 +117,10 @@ export class SlashCommandManager {
117
117
  }
118
118
 
119
119
  if (args) {
120
- if (hasParameterPlaceholders(processedContent)) {
121
- processedContent = substituteCommandParameters(
122
- processedContent,
123
- args,
124
- );
125
- } else {
126
- // If no placeholders, append arguments to the content
127
- processedContent = `${processedContent.trim()} ${args}`;
128
- }
120
+ processedContent = substituteCommandParameters(
121
+ processedContent,
122
+ args,
123
+ );
129
124
  }
130
125
 
131
126
  await this.executeCustomCommandInMainAgent(
@@ -240,15 +235,10 @@ export class SlashCommandManager {
240
235
  }
241
236
 
242
237
  if (args) {
243
- if (hasParameterPlaceholders(processedContent)) {
244
- processedContent = substituteCommandParameters(
245
- processedContent,
246
- args,
247
- );
248
- } else {
249
- // If no placeholders, append arguments to the content
250
- processedContent = `${processedContent.trim()} ${args}`;
251
- }
238
+ processedContent = substituteCommandParameters(
239
+ processedContent,
240
+ args,
241
+ );
252
242
  }
253
243
 
254
244
  await this.executeCustomCommandInMainAgent(
@@ -132,9 +132,9 @@ function getModelConfig(
132
132
  };
133
133
 
134
134
  // Configuration rules for specific models
135
- if (modelName.includes("gpt-5-codex")) {
136
- // gpt-5-codex model sets temperature to undefined
137
- config.temperature = undefined;
135
+ if (modelName.includes("gpt-5")) {
136
+ // gpt-5 models should not have temperature field
137
+ delete config.temperature;
138
138
  }
139
139
 
140
140
  return config;
@@ -399,6 +399,7 @@ export async function callAgent(
399
399
  }
400
400
  } catch (error) {
401
401
  if ((error as Error).name === "AbortError") {
402
+ logger.info("OpenAI request aborted");
402
403
  throw new Error("Request was aborted");
403
404
  }
404
405
 
@@ -818,6 +819,7 @@ export async function compressMessages(
818
819
  };
819
820
  } catch (error) {
820
821
  if ((error as Error).name === "AbortError") {
822
+ logger.info("Compression request was aborted");
821
823
  throw new Error("Compression request was aborted");
822
824
  }
823
825
  logger.error("Failed to compress messages:", error);
@@ -115,7 +115,6 @@ export class FileWatcherService extends EventEmitter {
115
115
  }
116
116
 
117
117
  this.watchers.delete(path);
118
- this.logger?.debug(`Live Config: Stopped watching file: ${path}`);
119
118
  } catch (error) {
120
119
  this.logger?.warn(
121
120
  `Live Config: Error unwatching file ${path}: ${(error as Error).message}`,
@@ -199,8 +198,6 @@ export class FileWatcherService extends EventEmitter {
199
198
  entry.watcher = this.globalWatcher;
200
199
  entry.isActive = true;
201
200
  entry.errorCount = 0;
202
-
203
- this.logger?.debug(`Live Config: Started watching file: ${entry.path}`);
204
201
  } catch (error) {
205
202
  entry.errorCount++;
206
203
  entry.isActive = false;
@@ -215,9 +212,6 @@ export class FileWatcherService extends EventEmitter {
215
212
  !entry.config.fallbackPolling &&
216
213
  entry.errorCount < entry.config.maxRetries
217
214
  ) {
218
- this.logger?.debug(
219
- `Live Config: Attempting polling fallback for ${entry.path}`,
220
- );
221
215
  entry.config.fallbackPolling = true;
222
216
  await this.initializeWatcher(entry);
223
217
  } else {
@@ -281,7 +275,5 @@ export class FileWatcherService extends EventEmitter {
281
275
  );
282
276
  }
283
277
  }
284
-
285
- this.logger?.debug(`Live Config: File ${type} event for ${filePath}`);
286
278
  }
287
279
  }
@@ -121,7 +121,6 @@ export class InitializationService {
121
121
  // Initialize hooks configuration
122
122
  try {
123
123
  // Load hooks configuration using ConfigurationService
124
- logger?.debug("Loading hooks configuration...");
125
124
  const configResult =
126
125
  await configurationService.loadMergedConfiguration(workdir);
127
126
 
@@ -156,8 +155,6 @@ export class InitializationService {
156
155
  }
157
156
  }
158
157
  }
159
-
160
- logger?.debug("Hooks system initialized successfully");
161
158
  } catch (error) {
162
159
  logger?.error("Failed to initialize hooks system:", error);
163
160
  // Don't throw error to prevent app startup failure
@@ -206,9 +203,7 @@ export class InitializationService {
206
203
 
207
204
  // Initialize live configuration reload
208
205
  try {
209
- logger?.debug("Initializing live configuration reload...");
210
206
  await liveConfigManager.initialize();
211
- logger?.debug("Live configuration reload initialized successfully");
212
207
  } catch (error) {
213
208
  logger?.error("Failed to initialize live configuration reload:", error);
214
209
  // Don't throw error to prevent app startup failure - continue without live reload
@@ -216,8 +211,6 @@ export class InitializationService {
216
211
 
217
212
  // Load memory files during initialization
218
213
  try {
219
- logger?.debug("Loading memory files...");
220
-
221
214
  // Load project memory from AGENTS.md (bypass memory store for direct file access)
222
215
  try {
223
216
  const projectMemoryPath = path.join(workdir, "AGENTS.md");
@@ -226,13 +219,9 @@ export class InitializationService {
226
219
  "utf-8",
227
220
  );
228
221
  setProjectMemory(projectMemoryContent);
229
- logger?.debug("Project memory loaded successfully");
230
222
  } catch (error) {
223
+ logger?.warn("Failed to load project memory file:", error);
231
224
  setProjectMemory("");
232
- logger?.debug(
233
- "Project memory file not found or unreadable, using empty content:",
234
- error instanceof Error ? error.message : String(error),
235
- );
236
225
  }
237
226
 
238
227
  // Load user memory (bypass memory store for direct file access)
@@ -240,16 +229,10 @@ export class InitializationService {
240
229
  const userMemoryPath = path.join(os.homedir(), ".wave", "AGENTS.md");
241
230
  const userMemoryContent = await fs.readFile(userMemoryPath, "utf-8");
242
231
  setUserMemory(userMemoryContent);
243
- logger?.debug("User memory loaded successfully");
244
232
  } catch (error) {
233
+ logger?.warn("Failed to load user memory file:", error);
245
234
  setUserMemory("");
246
- logger?.debug(
247
- "User memory file not found or unreadable, using empty content:",
248
- error instanceof Error ? error.message : String(error),
249
- );
250
235
  }
251
-
252
- logger?.debug("Memory initialization completed");
253
236
  } catch (error) {
254
237
  // Ensure memory is always initialized even if loading fails
255
238
  setProjectMemory("");
@@ -98,10 +98,8 @@ export class PluginLoader {
98
98
  });
99
99
  if (parsed.isValid) {
100
100
  skills.push({
101
- name: parsed.skillMetadata.name,
102
- description: parsed.skillMetadata.description,
101
+ ...parsed.skillMetadata,
103
102
  type: "project", // Plugin skills are treated as project skills
104
- skillPath: parsed.skillMetadata.skillPath,
105
103
  content: parsed.content,
106
104
  frontmatter: parsed.frontmatter,
107
105
  isValid: parsed.isValid,
@@ -161,6 +161,57 @@ export class TaskManager extends EventEmitter {
161
161
  });
162
162
  }
163
163
 
164
+ async deleteTask(taskId: string): Promise<void> {
165
+ await this.withLock(async () => {
166
+ const taskPath = this.getTaskPath(taskId);
167
+ try {
168
+ await fs.unlink(taskPath);
169
+ this.emit("tasksChange", this.taskListId);
170
+ logger.debug(
171
+ `Task ${taskId} deleted from task list ${this.taskListId}`,
172
+ );
173
+ } catch (error) {
174
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
175
+ throw error;
176
+ }
177
+ }
178
+ });
179
+ }
180
+
181
+ async cleanupOldTaskLists(days: number = 30): Promise<void> {
182
+ const threshold = Date.now() - days * 24 * 60 * 60 * 1000;
183
+ try {
184
+ const dirs = await fs.readdir(this.baseDir);
185
+ for (const dir of dirs) {
186
+ if (dir === this.taskListId) continue;
187
+
188
+ const dirPath = join(this.baseDir, dir);
189
+ const stats = await fs.stat(dirPath);
190
+ if (!stats.isDirectory()) continue;
191
+
192
+ // Check mtime of the directory and its contents
193
+ let latestMtime = stats.mtimeMs;
194
+ const files = await fs.readdir(dirPath);
195
+ for (const file of files) {
196
+ const filePath = join(dirPath, file);
197
+ const fileStats = await fs.stat(filePath);
198
+ if (fileStats.mtimeMs > latestMtime) {
199
+ latestMtime = fileStats.mtimeMs;
200
+ }
201
+ }
202
+
203
+ if (latestMtime < threshold) {
204
+ logger.info(`Cleaning up old task list directory: ${dirPath}`);
205
+ await fs.rm(dirPath, { recursive: true, force: true });
206
+ }
207
+ }
208
+ } catch (error) {
209
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
210
+ logger.error("Failed to cleanup old task lists:", error);
211
+ }
212
+ }
213
+ }
214
+
164
215
  async listTasks(): Promise<Task[]> {
165
216
  const sessionDir = this.getSessionDir();
166
217
  try {
@@ -5,6 +5,11 @@ import { resolvePath, getDisplayPath } from "../utils/path.js";
5
5
  import { getGlobIgnorePatterns } from "../utils/fileFilter.js";
6
6
  import { GLOB_TOOL_NAME } from "../constants/tools.js";
7
7
 
8
+ /**
9
+ * Maximum number of files returned by glob tool
10
+ */
11
+ const MAX_GLOB_RESULTS = 1000;
12
+
8
13
  /**
9
14
  * Glob Tool Plugin - Fast file pattern matching
10
15
  */
@@ -109,15 +114,23 @@ export const globTool: ToolPlugin = {
109
114
  .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()) // Most recently modified files first
110
115
  .map((item) => item.path);
111
116
 
117
+ const totalCount = sortedFiles.length;
118
+ const finalFiles = sortedFiles.slice(0, MAX_GLOB_RESULTS);
119
+
112
120
  // Format output
113
- const output = sortedFiles
121
+ const output = finalFiles
114
122
  .map((file, index) => `${index + 1}. ${file}`)
115
123
  .join("\n");
116
124
 
125
+ const isTruncated = totalCount > MAX_GLOB_RESULTS;
126
+ const shortResult = isTruncated
127
+ ? `Found ${totalCount} files (showing first ${MAX_GLOB_RESULTS})`
128
+ : `Found ${totalCount} file${totalCount === 1 ? "" : "s"}`;
129
+
117
130
  return {
118
131
  success: true,
119
132
  content: output,
120
- shortResult: `Found ${sortedFiles.length} file${sortedFiles.length === 1 ? "" : "s"}`,
133
+ shortResult,
121
134
  };
122
135
  } catch (error) {
123
136
  return {
@@ -101,6 +101,15 @@ NOTE that you should not use this tool if there is only one trivial task to do.
101
101
  - Check TaskList first to avoid creating duplicate tasks`,
102
102
  execute: async (args, context: ToolContext): Promise<ToolResult> => {
103
103
  const taskManager = context.taskManager;
104
+
105
+ if (args.status === "deleted") {
106
+ return {
107
+ success: true,
108
+ content: `Task creation skipped because status was set to 'deleted'.`,
109
+ shortResult: `Skipped deleted task`,
110
+ };
111
+ }
112
+
104
113
  const task: Omit<Task, "id"> = {
105
114
  subject: args.subject as string,
106
115
  description: args.description as string,
@@ -334,6 +343,74 @@ Set up task dependencies:
334
343
  };
335
344
  }
336
345
 
346
+ if (args.status === "deleted") {
347
+ // Reciprocal Dependency Cleanup
348
+ // For each task in the deleted task's blocks list, remove the deleted task's ID from their blockedBy list.
349
+ for (const targetId of existingTask.blocks) {
350
+ const targetTask = await taskManager.getTask(targetId);
351
+ if (targetTask && targetTask.blockedBy.includes(taskId)) {
352
+ let targetSnapshotId: string | undefined;
353
+ if (context.reversionManager && context.messageId) {
354
+ const targetPath = taskManager.getTaskPath(targetId);
355
+ targetSnapshotId = await context.reversionManager.recordSnapshot(
356
+ context.messageId,
357
+ targetPath,
358
+ "modify",
359
+ );
360
+ }
361
+ await taskManager.updateTask({
362
+ ...targetTask,
363
+ blockedBy: targetTask.blockedBy.filter((id) => id !== taskId),
364
+ });
365
+ if (context.reversionManager && targetSnapshotId) {
366
+ await context.reversionManager.commitSnapshot(targetSnapshotId);
367
+ }
368
+ }
369
+ }
370
+
371
+ // For each task in the deleted task's blockedBy list, remove the deleted task's ID from their blocks list.
372
+ for (const targetId of existingTask.blockedBy) {
373
+ const targetTask = await taskManager.getTask(targetId);
374
+ if (targetTask && targetTask.blocks.includes(taskId)) {
375
+ let targetSnapshotId: string | undefined;
376
+ if (context.reversionManager && context.messageId) {
377
+ const targetPath = taskManager.getTaskPath(targetId);
378
+ targetSnapshotId = await context.reversionManager.recordSnapshot(
379
+ context.messageId,
380
+ targetPath,
381
+ "modify",
382
+ );
383
+ }
384
+ await taskManager.updateTask({
385
+ ...targetTask,
386
+ blocks: targetTask.blocks.filter((id) => id !== taskId),
387
+ });
388
+ if (context.reversionManager && targetSnapshotId) {
389
+ await context.reversionManager.commitSnapshot(targetSnapshotId);
390
+ }
391
+ }
392
+ }
393
+
394
+ // Record delete snapshot for the task itself
395
+ if (context.reversionManager && context.messageId) {
396
+ const taskPath = taskManager.getTaskPath(taskId);
397
+ const deleteSnapshotId = await context.reversionManager.recordSnapshot(
398
+ context.messageId,
399
+ taskPath,
400
+ "delete",
401
+ );
402
+ await context.reversionManager.commitSnapshot(deleteSnapshotId);
403
+ }
404
+
405
+ await taskManager.deleteTask(taskId);
406
+
407
+ return {
408
+ success: true,
409
+ content: `Task #${taskId} deleted and removed from disk.`,
410
+ shortResult: `Deleted task ${taskId}`,
411
+ };
412
+ }
413
+
337
414
  let snapshotId: string | undefined;
338
415
  if (context.reversionManager && context.messageId) {
339
416
  const taskPath = taskManager.getTaskPath(taskId);