wave-agent-sdk 0.10.4 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builtin/skills/init/SKILL.md +26 -0
- package/builtin/skills/loop/SKILL.md +53 -0
- package/builtin/skills/settings/ENV.md +64 -0
- package/builtin/skills/settings/HOOKS.md +94 -0
- package/builtin/skills/settings/MCP.md +55 -0
- package/builtin/skills/settings/MEMORY_RULES.md +60 -0
- package/{dist/builtin-skills → builtin/skills}/settings/SKILL.md +23 -16
- package/builtin/skills/settings/SKILLS.md +63 -0
- package/builtin/skills/settings/SUBAGENTS.md +60 -0
- package/builtin/subagents/bash.md +18 -0
- package/builtin/subagents/explore.md +42 -0
- package/builtin/subagents/general-purpose.md +20 -0
- package/builtin/subagents/plan.md +55 -0
- package/dist/agent.d.ts +8 -6
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +12 -9
- package/dist/constants/tools.d.ts +3 -0
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/managers/aiManager.d.ts +0 -2
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +53 -14
- package/dist/managers/cronManager.d.ts +19 -0
- package/dist/managers/cronManager.d.ts.map +1 -0
- package/dist/managers/cronManager.js +124 -0
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +21 -13
- package/dist/managers/liveConfigManager.js +1 -1
- package/dist/managers/mcpManager.d.ts +1 -1
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +10 -2
- package/dist/managers/messageManager.d.ts +0 -1
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.d.ts +27 -7
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +119 -14
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +7 -12
- package/dist/managers/subagentManager.d.ts +3 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +10 -17
- package/dist/managers/toolManager.d.ts +1 -1
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +28 -4
- package/dist/prompts/index.d.ts +0 -5
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +1 -136
- package/dist/services/configurationService.d.ts.map +1 -1
- package/dist/services/configurationService.js +8 -7
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +3 -10
- package/dist/services/initializationService.js +2 -2
- package/dist/services/jsonlHandler.d.ts.map +1 -1
- package/dist/services/jsonlHandler.js +3 -0
- package/dist/services/reversionService.d.ts +2 -2
- package/dist/services/reversionService.d.ts.map +1 -1
- package/dist/services/reversionService.js +3 -3
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +18 -11
- package/dist/tools/agentTool.js +1 -1
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +5 -5
- package/dist/tools/cronCreateTool.d.ts +3 -0
- package/dist/tools/cronCreateTool.d.ts.map +1 -0
- package/dist/tools/cronCreateTool.js +59 -0
- package/dist/tools/cronDeleteTool.d.ts +3 -0
- package/dist/tools/cronDeleteTool.d.ts.map +1 -0
- package/dist/tools/cronDeleteTool.js +38 -0
- package/dist/tools/cronListTool.d.ts +3 -0
- package/dist/tools/cronListTool.d.ts.map +1 -0
- package/dist/tools/cronListTool.js +30 -0
- package/dist/tools/skillTool.d.ts +0 -3
- package/dist/tools/skillTool.d.ts.map +1 -1
- package/dist/tools/skillTool.js +4 -3
- package/dist/tools/taskOutputTool.d.ts.map +1 -1
- package/dist/tools/taskOutputTool.js +15 -8
- package/dist/tools/types.d.ts +2 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/agent.d.ts +10 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/configuration.d.ts +1 -1
- package/dist/types/configuration.d.ts.map +1 -1
- package/dist/types/cron.d.ts +10 -0
- package/dist/types/cron.d.ts.map +1 -0
- package/dist/types/cron.js +1 -0
- package/dist/types/hooks.d.ts +1 -5
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/messaging.d.ts +1 -1
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/utils/configPaths.d.ts +4 -0
- package/dist/utils/configPaths.d.ts.map +1 -1
- package/dist/utils/configPaths.js +11 -9
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +40 -13
- package/dist/utils/fileSearch.d.ts.map +1 -1
- package/dist/utils/fileSearch.js +7 -1
- package/dist/utils/mcpUtils.d.ts +2 -2
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +1 -5
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +14 -4
- package/package.json +4 -2
- package/src/agent.ts +17 -12
- package/src/constants/tools.ts +3 -0
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +72 -24
- package/src/managers/cronManager.ts +167 -0
- package/src/managers/hookManager.ts +27 -13
- package/src/managers/liveConfigManager.ts +2 -2
- package/src/managers/mcpManager.ts +23 -2
- package/src/managers/messageManager.ts +0 -6
- package/src/managers/permissionManager.ts +154 -18
- package/src/managers/slashCommandManager.ts +7 -14
- package/src/managers/subagentManager.ts +15 -19
- package/src/managers/toolManager.ts +37 -4
- package/src/prompts/index.ts +0 -144
- package/src/services/configurationService.ts +8 -7
- package/src/services/hook.ts +5 -11
- package/src/services/initializationService.ts +3 -3
- package/src/services/jsonlHandler.ts +4 -0
- package/src/services/reversionService.ts +9 -4
- package/src/services/session.ts +19 -12
- package/src/tools/agentTool.ts +1 -1
- package/src/tools/bashTool.ts +6 -5
- package/src/tools/cronCreateTool.ts +73 -0
- package/src/tools/cronDeleteTool.ts +47 -0
- package/src/tools/cronListTool.ts +38 -0
- package/src/tools/skillTool.ts +6 -4
- package/src/tools/taskOutputTool.ts +14 -8
- package/src/tools/types.ts +2 -0
- package/src/types/agent.ts +10 -0
- package/src/types/configuration.ts +1 -1
- package/src/types/cron.ts +9 -0
- package/src/types/hooks.ts +5 -9
- package/src/types/index.ts +1 -0
- package/src/types/messaging.ts +1 -1
- package/src/utils/configPaths.ts +12 -10
- package/src/utils/containerSetup.ts +50 -16
- package/src/utils/fileSearch.ts +7 -1
- package/src/utils/mcpUtils.ts +2 -5
- package/src/utils/subagentParser.ts +16 -6
- package/dist/builtin-skills/settings/HOOKS.md +0 -95
- package/dist/utils/builtinSubagents.d.ts +0 -7
- package/dist/utils/builtinSubagents.d.ts.map +0 -1
- package/dist/utils/builtinSubagents.js +0 -94
- package/src/builtin-skills/settings/HOOKS.md +0 -95
- package/src/builtin-skills/settings/SKILL.md +0 -86
- package/src/utils/builtinSubagents.ts +0 -122
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagentParser.d.ts","sourceRoot":"","sources":["../../src/utils/subagentParser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"subagentParser.d.ts","sourceRoot":"","sources":["../../src/utils/subagentParser.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAwLD;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAsBlC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAGvC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync } from "fs";
|
|
2
|
-
import { join, extname } from "path";
|
|
2
|
+
import { join, extname, basename } from "path";
|
|
3
3
|
import { logger } from "./globalLogger.js";
|
|
4
|
+
import { getBuiltinSubagentsDir } from "./configPaths.js";
|
|
4
5
|
/**
|
|
5
6
|
* Parse YAML frontmatter from markdown file content
|
|
6
7
|
*/
|
|
@@ -85,10 +86,19 @@ function parseSubagentFile(filePath, scope) {
|
|
|
85
86
|
try {
|
|
86
87
|
const content = readFileSync(filePath, "utf-8");
|
|
87
88
|
const { frontmatter, body } = parseFrontmatter(content);
|
|
89
|
+
// Use filename as default name if not specified in frontmatter
|
|
90
|
+
if (!frontmatter.name) {
|
|
91
|
+
frontmatter.name = basename(filePath, extname(filePath));
|
|
92
|
+
}
|
|
88
93
|
validateConfiguration(frontmatter, filePath);
|
|
89
94
|
if (!body.trim()) {
|
|
90
95
|
throw new Error(`Empty system prompt in ${filePath}`);
|
|
91
96
|
}
|
|
97
|
+
let priority = 1;
|
|
98
|
+
if (scope === "user")
|
|
99
|
+
priority = 2;
|
|
100
|
+
if (scope === "builtin")
|
|
101
|
+
priority = 3;
|
|
92
102
|
return {
|
|
93
103
|
name: frontmatter.name,
|
|
94
104
|
description: frontmatter.description,
|
|
@@ -97,7 +107,7 @@ function parseSubagentFile(filePath, scope) {
|
|
|
97
107
|
systemPrompt: body,
|
|
98
108
|
filePath,
|
|
99
109
|
scope,
|
|
100
|
-
priority
|
|
110
|
+
priority,
|
|
101
111
|
};
|
|
102
112
|
}
|
|
103
113
|
catch (error) {
|
|
@@ -137,9 +147,9 @@ function scanSubagentDirectory(dirPath, scope) {
|
|
|
137
147
|
export async function loadSubagentConfigurations(workdir) {
|
|
138
148
|
const projectDir = join(workdir, ".wave", "agents");
|
|
139
149
|
const userDir = join(process.env.HOME || "~", ".wave", "agents");
|
|
150
|
+
const builtinDir = getBuiltinSubagentsDir();
|
|
140
151
|
// Load configurations from all sources
|
|
141
|
-
const
|
|
142
|
-
const builtinConfigs = getBuiltinSubagents();
|
|
152
|
+
const builtinConfigs = scanSubagentDirectory(builtinDir, "builtin");
|
|
143
153
|
const projectConfigs = scanSubagentDirectory(projectDir, "project");
|
|
144
154
|
const userConfigs = scanSubagentDirectory(userDir, "user");
|
|
145
155
|
// Merge configurations, with project configs taking highest precedence
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-agent-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "SDK for building AI-powered development tools and agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -24,11 +24,13 @@
|
|
|
24
24
|
"bin",
|
|
25
25
|
"vendor",
|
|
26
26
|
"scripts",
|
|
27
|
+
"builtin",
|
|
27
28
|
"README.md"
|
|
28
29
|
],
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@modelcontextprotocol/sdk": "^1.18.2",
|
|
31
32
|
"chokidar": "^5.0.0",
|
|
33
|
+
"cron-parser": "^5.5.0",
|
|
32
34
|
"fuzzysort": "^3.1.0",
|
|
33
35
|
"glob": "^13.0.0",
|
|
34
36
|
"minimatch": "^10.0.3",
|
|
@@ -47,7 +49,7 @@
|
|
|
47
49
|
"license": "MIT",
|
|
48
50
|
"scripts": {
|
|
49
51
|
"postinstall": "node scripts/postinstall.js",
|
|
50
|
-
"build": "rimraf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json
|
|
52
|
+
"build": "rimraf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
51
53
|
"type-check": "tsc --noEmit --incremental",
|
|
52
54
|
"watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
|
|
53
55
|
"test": "vitest run --reporter=dot",
|
package/src/agent.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { SubagentManager } from "./managers/subagentManager.js";
|
|
|
6
6
|
import { McpManager } from "./managers/mcpManager.js";
|
|
7
7
|
import { LspManager } from "./managers/lspManager.js";
|
|
8
8
|
import { BangManager } from "./managers/bangManager.js";
|
|
9
|
+
import { CronManager } from "./managers/cronManager.js";
|
|
9
10
|
import { BackgroundTaskManager } from "./managers/backgroundTaskManager.js";
|
|
10
11
|
import { SlashCommandManager } from "./managers/slashCommandManager.js";
|
|
11
12
|
import { PluginManager } from "./managers/pluginManager.js";
|
|
@@ -56,6 +57,7 @@ export class Agent {
|
|
|
56
57
|
private slashCommandManager: SlashCommandManager; // Add slash command manager instance
|
|
57
58
|
private pluginManager: PluginManager; // Add plugin manager instance
|
|
58
59
|
private skillManager: SkillManager; // Add skill manager instance
|
|
60
|
+
private cronManager: CronManager; // Add cron manager instance
|
|
59
61
|
private hookManager: HookManager; // Add hooks manager instance
|
|
60
62
|
private reversionManager: ReversionManager;
|
|
61
63
|
private memoryRuleManager: MemoryRuleManager; // Add memory rule manager instance
|
|
@@ -168,6 +170,7 @@ export class Agent {
|
|
|
168
170
|
this.slashCommandManager = this.container.get("SlashCommandManager")!;
|
|
169
171
|
this.pluginManager = this.container.get("PluginManager")!;
|
|
170
172
|
this.bangManager = this.container.get("BangManager")!;
|
|
173
|
+
this.cronManager = this.container.get("CronManager")!;
|
|
171
174
|
|
|
172
175
|
// Set initial permission mode if provided
|
|
173
176
|
if (options.permissionMode) {
|
|
@@ -411,8 +414,8 @@ export class Agent {
|
|
|
411
414
|
await this.bangManager?.executeCommand(command);
|
|
412
415
|
}
|
|
413
416
|
|
|
414
|
-
public clearMessages(): void {
|
|
415
|
-
this.
|
|
417
|
+
public async clearMessages(): Promise<void> {
|
|
418
|
+
await this.slashCommandManager.executeCommand("clear");
|
|
416
419
|
}
|
|
417
420
|
|
|
418
421
|
/** Unified interrupt method, interrupts both AI messages and command execution */
|
|
@@ -460,6 +463,7 @@ export class Agent {
|
|
|
460
463
|
this.abortAIMessage(); // This will abort tools including Agent tool (subagents)
|
|
461
464
|
this.abortBashCommand();
|
|
462
465
|
this.abortSlashCommand();
|
|
466
|
+
this.cronManager.stop();
|
|
463
467
|
// Cleanup background task manager
|
|
464
468
|
this.backgroundTaskManager.cleanup();
|
|
465
469
|
// Cleanup MCP connections
|
|
@@ -484,16 +488,6 @@ export class Agent {
|
|
|
484
488
|
// Cleanup memory store
|
|
485
489
|
}
|
|
486
490
|
|
|
487
|
-
/**
|
|
488
|
-
* Get a subagent instance by its ID
|
|
489
|
-
* @param subagentId - The ID of the subagent instance
|
|
490
|
-
*/
|
|
491
|
-
public getSubagentInstance(
|
|
492
|
-
subagentId: string,
|
|
493
|
-
): import("./managers/subagentManager.js").SubagentInstance | null {
|
|
494
|
-
return this.subagentManager.getInstance(subagentId);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
491
|
/**
|
|
498
492
|
* Trigger the rewind UI callback
|
|
499
493
|
*/
|
|
@@ -688,6 +682,17 @@ export class Agent {
|
|
|
688
682
|
await this.permissionManager.addPermissionRule(rule);
|
|
689
683
|
}
|
|
690
684
|
|
|
685
|
+
/**
|
|
686
|
+
* Get subagent instance by ID
|
|
687
|
+
* @param subagentId - The ID of the subagent instance
|
|
688
|
+
* @returns The subagent instance or null if not found
|
|
689
|
+
*/
|
|
690
|
+
public getSubagentInstance(
|
|
691
|
+
subagentId: string,
|
|
692
|
+
): import("./managers/subagentManager.js").SubagentInstance | null {
|
|
693
|
+
return this.subagentManager.getInstance(subagentId);
|
|
694
|
+
}
|
|
695
|
+
|
|
691
696
|
/**
|
|
692
697
|
* Get the current task list ID
|
|
693
698
|
*/
|
package/src/constants/tools.ts
CHANGED
|
@@ -15,3 +15,6 @@ export const TASK_GET_TOOL_NAME = "TaskGet";
|
|
|
15
15
|
export const TASK_UPDATE_TOOL_NAME = "TaskUpdate";
|
|
16
16
|
export const TASK_LIST_TOOL_NAME = "TaskList";
|
|
17
17
|
export const WRITE_TOOL_NAME = "Write";
|
|
18
|
+
export const CRON_CREATE_TOOL_NAME = "CronCreate";
|
|
19
|
+
export const CRON_DELETE_TOOL_NAME = "CronDelete";
|
|
20
|
+
export const CRON_LIST_TOOL_NAME = "CronList";
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
ModelConfig,
|
|
9
9
|
Usage,
|
|
10
10
|
PermissionMode,
|
|
11
|
+
Message,
|
|
11
12
|
} from "../types/index.js";
|
|
12
13
|
import type { ToolManager } from "./toolManager.js";
|
|
13
14
|
import type { ToolContext, ToolResult } from "../tools/types.js";
|
|
@@ -160,26 +161,18 @@ export class AIManager {
|
|
|
160
161
|
/**
|
|
161
162
|
* Get filtered tool configuration based on tools list
|
|
162
163
|
*/
|
|
163
|
-
private getFilteredToolsConfig(
|
|
164
|
+
private getFilteredToolsConfig() {
|
|
164
165
|
// Get available subagents and skills for dynamic prompts
|
|
165
166
|
const availableSubagents = this.subagentManager?.getConfigurations();
|
|
166
167
|
const availableSkills = this.skillManager
|
|
167
168
|
?.getAvailableSkills()
|
|
168
169
|
.filter((skill) => !skill.disableModelInvocation);
|
|
169
170
|
|
|
170
|
-
|
|
171
|
+
return this.toolManager.getToolsConfig({
|
|
171
172
|
availableSubagents,
|
|
172
173
|
availableSkills,
|
|
173
174
|
workdir: this.workdir,
|
|
174
175
|
});
|
|
175
|
-
|
|
176
|
-
// If no tools specified, return all tools
|
|
177
|
-
if (!tools || tools.length === 0) {
|
|
178
|
-
return allTools;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Filter tools
|
|
182
|
-
return allTools.filter((tool) => tools.includes(tool.function.name));
|
|
183
176
|
}
|
|
184
177
|
|
|
185
178
|
public setIsLoading(isLoading: boolean): void {
|
|
@@ -336,18 +329,10 @@ export class AIManager {
|
|
|
336
329
|
model?: string;
|
|
337
330
|
/** Rules for automatic tool approval (e.g., "Bash(git status*)") */
|
|
338
331
|
allowedRules?: string[];
|
|
339
|
-
/** List of tools available to the AI (e.g., ["Bash", "Read"]) */
|
|
340
|
-
tools?: string[];
|
|
341
332
|
maxTokens?: number;
|
|
342
333
|
} = {},
|
|
343
334
|
): Promise<void> {
|
|
344
|
-
const {
|
|
345
|
-
recursionDepth = 0,
|
|
346
|
-
model,
|
|
347
|
-
allowedRules,
|
|
348
|
-
tools,
|
|
349
|
-
maxTokens,
|
|
350
|
-
} = options;
|
|
335
|
+
const { recursionDepth = 0, model, allowedRules, maxTokens } = options;
|
|
351
336
|
|
|
352
337
|
// Only check isLoading for the initial call (recursionDepth === 0)
|
|
353
338
|
if (recursionDepth === 0 && this.isLoading) {
|
|
@@ -401,7 +386,7 @@ export class AIManager {
|
|
|
401
386
|
const currentMode = this.permissionManager?.getCurrentEffectiveMode(
|
|
402
387
|
this.getModelConfig().permissionMode,
|
|
403
388
|
);
|
|
404
|
-
const toolsConfig = this.getFilteredToolsConfig(
|
|
389
|
+
const toolsConfig = this.getFilteredToolsConfig();
|
|
405
390
|
const toolNames = new Set(toolsConfig.map((t) => t.function.name));
|
|
406
391
|
const filteredToolPlugins = this.toolManager
|
|
407
392
|
.getTools()
|
|
@@ -489,7 +474,6 @@ export class AIManager {
|
|
|
489
474
|
name: toolCall.name,
|
|
490
475
|
parameters: toolCall.parameters,
|
|
491
476
|
parametersChunk: toolCall.parametersChunk,
|
|
492
|
-
compactParams: toolCall.parameters?.split("\n").pop()?.slice(-30),
|
|
493
477
|
stage: toolCall.stage || "streaming", // Default to streaming if stage not provided
|
|
494
478
|
});
|
|
495
479
|
};
|
|
@@ -655,7 +639,18 @@ export class AIManager {
|
|
|
655
639
|
toolArgs,
|
|
656
640
|
);
|
|
657
641
|
|
|
658
|
-
// Emit
|
|
642
|
+
// Emit start stage for non-streaming tool calls
|
|
643
|
+
if (!this.stream) {
|
|
644
|
+
this.messageManager.updateToolBlock({
|
|
645
|
+
id: toolId,
|
|
646
|
+
stage: "start",
|
|
647
|
+
name: toolName,
|
|
648
|
+
compactParams,
|
|
649
|
+
parameters: argsString,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Emit running stage (tool execution about to start)
|
|
659
654
|
this.messageManager.updateToolBlock({
|
|
660
655
|
id: toolId,
|
|
661
656
|
stage: "running",
|
|
@@ -717,6 +712,7 @@ export class AIManager {
|
|
|
717
712
|
error: toolResult.error,
|
|
718
713
|
stage: "end",
|
|
719
714
|
name: toolName,
|
|
715
|
+
compactParams,
|
|
720
716
|
shortResult: toolResult.shortResult,
|
|
721
717
|
isManuallyBackgrounded: toolResult.isManuallyBackgrounded,
|
|
722
718
|
startLineNumber: toolResult.startLineNumber,
|
|
@@ -803,12 +799,65 @@ export class AIManager {
|
|
|
803
799
|
});
|
|
804
800
|
}
|
|
805
801
|
|
|
802
|
+
// Duplicate Tool Call Detection
|
|
803
|
+
if (toolCalls.length > 0) {
|
|
804
|
+
const messages = this.messageManager.getMessages();
|
|
805
|
+
// Find the most recent assistant message BEFORE the current one that has tool blocks
|
|
806
|
+
// The current assistant message is messages[messages.length - 1]
|
|
807
|
+
let previousAssistantWithTools: Message | undefined;
|
|
808
|
+
for (let i = messages.length - 2; i >= 0; i--) {
|
|
809
|
+
const msg = messages[i];
|
|
810
|
+
if (
|
|
811
|
+
msg.role === "assistant" &&
|
|
812
|
+
msg.blocks.some((b) => b.type === "tool")
|
|
813
|
+
) {
|
|
814
|
+
previousAssistantWithTools = msg;
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (previousAssistantWithTools) {
|
|
820
|
+
const previousToolBlocks =
|
|
821
|
+
previousAssistantWithTools.blocks.filter(
|
|
822
|
+
(b): b is import("../types/messaging.js").ToolBlock =>
|
|
823
|
+
b.type === "tool",
|
|
824
|
+
);
|
|
825
|
+
|
|
826
|
+
for (const currentToolCall of toolCalls) {
|
|
827
|
+
const currentName = currentToolCall.function?.name;
|
|
828
|
+
const currentArgs = currentToolCall.function?.arguments;
|
|
829
|
+
|
|
830
|
+
const isDuplicate = previousToolBlocks.some(
|
|
831
|
+
(prevBlock) =>
|
|
832
|
+
prevBlock.name === currentName &&
|
|
833
|
+
prevBlock.parameters === currentArgs,
|
|
834
|
+
);
|
|
835
|
+
|
|
836
|
+
if (isDuplicate && currentName) {
|
|
837
|
+
const toolId = currentToolCall.id;
|
|
838
|
+
const lastMessage = messages[messages.length - 1];
|
|
839
|
+
const toolBlock = lastMessage.blocks.find(
|
|
840
|
+
(b): b is import("../types/messaging.js").ToolBlock =>
|
|
841
|
+
b.type === "tool" && b.id === toolId,
|
|
842
|
+
);
|
|
843
|
+
if (toolBlock) {
|
|
844
|
+
const warning = `\n\nNote: You just called this tool with the same arguments in the previous turn. Please ensure you are not in a loop and consider if you need to change your approach.`;
|
|
845
|
+
this.messageManager.updateToolBlock({
|
|
846
|
+
id: toolId,
|
|
847
|
+
result: (toolBlock.result || "") + warning,
|
|
848
|
+
stage: "end",
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
806
856
|
// Recursively call AI service, increment recursion depth, and pass same configuration
|
|
807
857
|
await this.sendAIMessage({
|
|
808
858
|
recursionDepth: recursionDepth + 1,
|
|
809
859
|
model,
|
|
810
860
|
allowedRules,
|
|
811
|
-
tools,
|
|
812
861
|
maxTokens,
|
|
813
862
|
});
|
|
814
863
|
}
|
|
@@ -861,7 +910,6 @@ export class AIManager {
|
|
|
861
910
|
recursionDepth: 0,
|
|
862
911
|
model,
|
|
863
912
|
allowedRules,
|
|
864
|
-
tools,
|
|
865
913
|
maxTokens,
|
|
866
914
|
});
|
|
867
915
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Container } from "../utils/container.js";
|
|
2
|
+
import { CronJob } from "../types/cron.js";
|
|
3
|
+
import { AIManager } from "./aiManager.js";
|
|
4
|
+
import { MessageManager } from "./messageManager.js";
|
|
5
|
+
import { CronExpressionParser } from "cron-parser";
|
|
6
|
+
import { logger } from "../utils/globalLogger.js";
|
|
7
|
+
|
|
8
|
+
export class CronManager {
|
|
9
|
+
private jobs = new Map<string, CronJob>();
|
|
10
|
+
private interval: NodeJS.Timeout | null = null;
|
|
11
|
+
|
|
12
|
+
constructor(private container: Container) {}
|
|
13
|
+
|
|
14
|
+
private get aiManager(): AIManager {
|
|
15
|
+
return this.container.get<AIManager>("AIManager")!;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private get messageManager(): MessageManager {
|
|
19
|
+
return this.container.get<MessageManager>("MessageManager")!;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public start(): void {
|
|
23
|
+
if (this.interval) return;
|
|
24
|
+
this.interval = setInterval(() => this.checkJobs(), 60000); // Check every minute
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public stop(): void {
|
|
28
|
+
if (this.interval) {
|
|
29
|
+
clearInterval(this.interval);
|
|
30
|
+
this.interval = null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public createJob(
|
|
35
|
+
job: Omit<CronJob, "id" | "createdAt" | "nextRun" | "periodMs">,
|
|
36
|
+
): CronJob {
|
|
37
|
+
const id = Math.random().toString(36).substring(2, 11);
|
|
38
|
+
const createdAt = Date.now();
|
|
39
|
+
|
|
40
|
+
const interval = CronExpressionParser.parse(job.cron);
|
|
41
|
+
const nextRunDate = interval.next().toDate();
|
|
42
|
+
const nextRun = nextRunDate.getTime();
|
|
43
|
+
|
|
44
|
+
// Calculate periodMs
|
|
45
|
+
const secondRunDate = interval.next().toDate();
|
|
46
|
+
const periodMs = secondRunDate.getTime() - nextRunDate.getTime();
|
|
47
|
+
|
|
48
|
+
// Apply Jitter
|
|
49
|
+
const jitteredNextRun = this.applyJitter(
|
|
50
|
+
nextRun,
|
|
51
|
+
periodMs,
|
|
52
|
+
job.recurring,
|
|
53
|
+
nextRunDate,
|
|
54
|
+
id,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const newJob: CronJob = {
|
|
58
|
+
...job,
|
|
59
|
+
id,
|
|
60
|
+
createdAt,
|
|
61
|
+
nextRun: jitteredNextRun,
|
|
62
|
+
periodMs,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
this.jobs.set(id, newJob);
|
|
66
|
+
return newJob;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public deleteJob(id: string): boolean {
|
|
70
|
+
return this.jobs.delete(id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public listJobs(): CronJob[] {
|
|
74
|
+
return Array.from(this.jobs.values());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private applyJitter(
|
|
78
|
+
nextRun: number,
|
|
79
|
+
periodMs: number,
|
|
80
|
+
recurring: boolean,
|
|
81
|
+
nextRunDate: Date,
|
|
82
|
+
id: string,
|
|
83
|
+
): number {
|
|
84
|
+
const deterministicRandom = this.getDeterministicRandom(id);
|
|
85
|
+
if (recurring) {
|
|
86
|
+
// Recurring: Random delay up to 10% of period (max 15 min)
|
|
87
|
+
const maxJitter = Math.min(periodMs * 0.1, 15 * 60 * 1000);
|
|
88
|
+
return nextRun + deterministicRandom * maxJitter;
|
|
89
|
+
} else {
|
|
90
|
+
// One-shot: Random early fire up to 90s if scheduled on :00 or :30
|
|
91
|
+
const minutes = nextRunDate.getMinutes();
|
|
92
|
+
const seconds = nextRunDate.getSeconds();
|
|
93
|
+
if ((minutes === 0 || minutes === 30) && seconds === 0) {
|
|
94
|
+
return nextRun - deterministicRandom * 90 * 1000;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return nextRun;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private getDeterministicRandom(id: string): number {
|
|
101
|
+
let hash = 0;
|
|
102
|
+
for (let i = 0; i < id.length; i++) {
|
|
103
|
+
const char = id.charCodeAt(i);
|
|
104
|
+
hash = (hash << 5) - hash + char;
|
|
105
|
+
hash |= 0; // Convert to 32bit integer
|
|
106
|
+
}
|
|
107
|
+
// Use a simple LCG-like approach to get a value between 0 and 1
|
|
108
|
+
const x = Math.sin(hash) * 10000;
|
|
109
|
+
return x - Math.floor(x);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async checkJobs(): Promise<void> {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const aiManager = this.aiManager;
|
|
115
|
+
const messageManager = this.messageManager;
|
|
116
|
+
|
|
117
|
+
for (const [id, job] of this.jobs.entries()) {
|
|
118
|
+
// Expiration: Recurring jobs MUST auto-expire after 7 days
|
|
119
|
+
if (job.recurring && now - job.createdAt > 7 * 24 * 60 * 60 * 1000) {
|
|
120
|
+
this.jobs.delete(id);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (now >= job.nextRun) {
|
|
125
|
+
// Idle-Check: Only fire jobs if AIManager.isLoading is false
|
|
126
|
+
if (aiManager.isLoading) {
|
|
127
|
+
logger?.debug(`CronManager: Skipping job ${id} because AI is busy`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
logger?.info(`CronManager: Firing job ${id}: ${job.prompt}`);
|
|
132
|
+
|
|
133
|
+
// Execution
|
|
134
|
+
messageManager.addUserMessage({ content: job.prompt });
|
|
135
|
+
aiManager.sendAIMessage().catch((err) => {
|
|
136
|
+
logger?.error(`CronManager: Failed to execute job ${id}`, err);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (job.recurring) {
|
|
140
|
+
// Schedule next run
|
|
141
|
+
try {
|
|
142
|
+
const interval = CronExpressionParser.parse(job.cron, {
|
|
143
|
+
currentDate: new Date(job.nextRun + 1000),
|
|
144
|
+
});
|
|
145
|
+
const nextRunDate = interval.next().toDate();
|
|
146
|
+
const nextRun = nextRunDate.getTime();
|
|
147
|
+
job.nextRun = this.applyJitter(
|
|
148
|
+
nextRun,
|
|
149
|
+
job.periodMs,
|
|
150
|
+
true,
|
|
151
|
+
nextRunDate,
|
|
152
|
+
id,
|
|
153
|
+
);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
logger?.error(
|
|
156
|
+
`CronManager: Failed to parse cron for recurring job ${id}`,
|
|
157
|
+
e,
|
|
158
|
+
);
|
|
159
|
+
this.jobs.delete(id);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
this.jobs.delete(id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -236,13 +236,17 @@ export class HookManager {
|
|
|
236
236
|
for (const result of results) {
|
|
237
237
|
if (result.exitCode === 2) {
|
|
238
238
|
// Handle blocking error immediately and return
|
|
239
|
-
|
|
239
|
+
const blockingResult = this.handleBlockingError(
|
|
240
240
|
event,
|
|
241
241
|
result,
|
|
242
242
|
messageManager,
|
|
243
243
|
toolId,
|
|
244
244
|
toolParameters,
|
|
245
245
|
);
|
|
246
|
+
return {
|
|
247
|
+
shouldBlock: blockingResult.shouldBlock,
|
|
248
|
+
errorMessage: blockingResult.errorMessage,
|
|
249
|
+
};
|
|
246
250
|
}
|
|
247
251
|
}
|
|
248
252
|
|
|
@@ -280,7 +284,7 @@ export class HookManager {
|
|
|
280
284
|
source: MessageSource.HOOK,
|
|
281
285
|
});
|
|
282
286
|
}
|
|
283
|
-
// For other hook types (PreToolUse, PostToolUse, Stop), ignore stdout
|
|
287
|
+
// For other hook types (PreToolUse, PostToolUse, Stop, PermissionRequest), ignore stdout
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
/**
|
|
@@ -338,10 +342,10 @@ export class HookManager {
|
|
|
338
342
|
});
|
|
339
343
|
return { shouldBlock: true, errorMessage };
|
|
340
344
|
|
|
341
|
-
case "
|
|
342
|
-
// For
|
|
345
|
+
case "PermissionRequest":
|
|
346
|
+
// For permission request hooks with exit code 2, show stderr in error block and block (deny) permission
|
|
343
347
|
messageManager.addErrorBlock(errorMessage);
|
|
344
|
-
return { shouldBlock:
|
|
348
|
+
return { shouldBlock: true, errorMessage };
|
|
345
349
|
|
|
346
350
|
case "SubagentStop":
|
|
347
351
|
// Similar to Stop, show error and allow blocking
|
|
@@ -544,7 +548,11 @@ export class HookManager {
|
|
|
544
548
|
}
|
|
545
549
|
|
|
546
550
|
// Validate tool-specific requirements
|
|
547
|
-
if (
|
|
551
|
+
if (
|
|
552
|
+
event === "PreToolUse" ||
|
|
553
|
+
event === "PostToolUse" ||
|
|
554
|
+
event === "PermissionRequest"
|
|
555
|
+
) {
|
|
548
556
|
if (!context.toolName || typeof context.toolName !== "string") {
|
|
549
557
|
errors.push(`${event} event requires a valid toolName in context`);
|
|
550
558
|
}
|
|
@@ -554,7 +562,6 @@ export class HookManager {
|
|
|
554
562
|
if (
|
|
555
563
|
(event === "UserPromptSubmit" ||
|
|
556
564
|
event === "Stop" ||
|
|
557
|
-
event === "Notification" ||
|
|
558
565
|
event === "SubagentStop" ||
|
|
559
566
|
event === "WorktreeCreate") &&
|
|
560
567
|
context.toolName !== undefined
|
|
@@ -631,7 +638,6 @@ export class HookManager {
|
|
|
631
638
|
if (
|
|
632
639
|
event === "UserPromptSubmit" ||
|
|
633
640
|
event === "Stop" ||
|
|
634
|
-
event === "Notification" ||
|
|
635
641
|
event === "SubagentStop" ||
|
|
636
642
|
event === "WorktreeCreate"
|
|
637
643
|
) {
|
|
@@ -639,7 +645,11 @@ export class HookManager {
|
|
|
639
645
|
}
|
|
640
646
|
|
|
641
647
|
// For tool-based events, check matcher if present
|
|
642
|
-
if (
|
|
648
|
+
if (
|
|
649
|
+
event === "PreToolUse" ||
|
|
650
|
+
event === "PostToolUse" ||
|
|
651
|
+
event === "PermissionRequest"
|
|
652
|
+
) {
|
|
643
653
|
if (!config.matcher) {
|
|
644
654
|
// No matcher means applies to all tools
|
|
645
655
|
return true;
|
|
@@ -673,7 +683,12 @@ export class HookManager {
|
|
|
673
683
|
}
|
|
674
684
|
|
|
675
685
|
// Validate matcher requirements
|
|
676
|
-
if (
|
|
686
|
+
if (
|
|
687
|
+
(event === "PreToolUse" ||
|
|
688
|
+
event === "PostToolUse" ||
|
|
689
|
+
event === "PermissionRequest") &&
|
|
690
|
+
config.matcher
|
|
691
|
+
) {
|
|
677
692
|
if (!this.matcher.isValidPattern(config.matcher)) {
|
|
678
693
|
errors.push(`${prefix}: Invalid matcher pattern: ${config.matcher}`);
|
|
679
694
|
}
|
|
@@ -683,7 +698,6 @@ export class HookManager {
|
|
|
683
698
|
if (
|
|
684
699
|
(event === "UserPromptSubmit" ||
|
|
685
700
|
event === "Stop" ||
|
|
686
|
-
event === "Notification" ||
|
|
687
701
|
event === "SubagentStop" ||
|
|
688
702
|
event === "WorktreeCreate") &&
|
|
689
703
|
config.matcher
|
|
@@ -723,7 +737,7 @@ export class HookManager {
|
|
|
723
737
|
UserPromptSubmit: 0,
|
|
724
738
|
Stop: 0,
|
|
725
739
|
SubagentStop: 0,
|
|
726
|
-
|
|
740
|
+
PermissionRequest: 0,
|
|
727
741
|
WorktreeCreate: 0,
|
|
728
742
|
},
|
|
729
743
|
};
|
|
@@ -735,7 +749,7 @@ export class HookManager {
|
|
|
735
749
|
UserPromptSubmit: 0,
|
|
736
750
|
Stop: 0,
|
|
737
751
|
SubagentStop: 0,
|
|
738
|
-
|
|
752
|
+
PermissionRequest: 0,
|
|
739
753
|
WorktreeCreate: 0,
|
|
740
754
|
};
|
|
741
755
|
|
|
@@ -258,8 +258,8 @@ export class LiveConfigManager {
|
|
|
258
258
|
|
|
259
259
|
// Update permission manager if available
|
|
260
260
|
if (this.permissionManager) {
|
|
261
|
-
this.permissionManager.
|
|
262
|
-
this.currentConfiguration.permissions?.
|
|
261
|
+
this.permissionManager.updateConfiguredPermissionMode(
|
|
262
|
+
this.currentConfiguration.permissions?.permissionMode,
|
|
263
263
|
);
|
|
264
264
|
this.permissionManager.updateAllowedRules(
|
|
265
265
|
this.currentConfiguration.permissions?.allow || [],
|