wave-agent-sdk 0.15.1 → 0.15.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.
- package/builtin/skills/loop/SKILL.md +29 -3
- package/dist/agent.d.ts +7 -2
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +34 -11
- 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 +13 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +69 -17
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +9 -0
- package/dist/managers/mcpManager.d.ts +4 -1
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +25 -5
- package/dist/managers/permissionManager.d.ts +0 -2
- package/dist/managers/permissionManager.d.ts.map +1 -1
- package/dist/managers/permissionManager.js +0 -30
- package/dist/managers/slashCommandManager.d.ts +1 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +4 -0
- package/dist/managers/toolManager.d.ts +6 -0
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +41 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +14 -4
- package/dist/services/initializationService.d.ts +0 -2
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +3 -35
- package/dist/services/memory.d.ts +6 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +27 -14
- package/dist/tools/cronCreateTool.d.ts.map +1 -1
- package/dist/tools/cronCreateTool.js +71 -6
- package/dist/tools/cronDeleteTool.d.ts.map +1 -1
- package/dist/tools/cronDeleteTool.js +5 -1
- package/dist/tools/cronListTool.d.ts.map +1 -1
- package/dist/tools/cronListTool.js +5 -1
- package/dist/tools/enterWorktreeTool.d.ts +8 -0
- package/dist/tools/enterWorktreeTool.d.ts.map +1 -0
- package/dist/tools/enterWorktreeTool.js +144 -0
- package/dist/tools/exitWorktreeTool.d.ts +8 -0
- package/dist/tools/exitWorktreeTool.d.ts.map +1 -0
- package/dist/tools/exitWorktreeTool.js +184 -0
- package/dist/tools/taskManagementTools.d.ts.map +1 -1
- package/dist/tools/taskManagementTools.js +4 -0
- package/dist/tools/toolSearchTool.d.ts +15 -0
- package/dist/tools/toolSearchTool.d.ts.map +1 -0
- package/dist/tools/toolSearchTool.js +185 -0
- package/dist/tools/types.d.ts +19 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts.map +1 -1
- package/dist/tools/webFetchTool.js +1 -0
- package/dist/types/agent.d.ts +6 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/hooks.d.ts +3 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +1 -0
- package/dist/utils/containerSetup.d.ts.map +1 -1
- package/dist/utils/containerSetup.js +4 -6
- package/dist/utils/cronToHuman.d.ts +6 -0
- package/dist/utils/cronToHuman.d.ts.map +1 -0
- package/dist/utils/cronToHuman.js +79 -0
- package/dist/utils/isDeferredTool.d.ts +19 -0
- package/dist/utils/isDeferredTool.d.ts.map +1 -0
- package/dist/utils/isDeferredTool.js +31 -0
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +1 -0
- package/dist/utils/parseCronExpression.d.ts +6 -0
- package/dist/utils/parseCronExpression.d.ts.map +1 -0
- package/dist/utils/parseCronExpression.js +74 -0
- package/dist/utils/worktreeSession.d.ts +26 -0
- package/dist/utils/worktreeSession.d.ts.map +1 -0
- package/dist/utils/worktreeSession.js +14 -0
- package/dist/utils/worktreeUtils.d.ts +42 -0
- package/dist/utils/worktreeUtils.d.ts.map +1 -0
- package/dist/utils/worktreeUtils.js +236 -0
- package/package.json +1 -1
- package/src/agent.ts +49 -12
- package/src/constants/tools.ts +3 -0
- package/src/index.ts +1 -0
- package/src/managers/aiManager.ts +73 -18
- package/src/managers/hookManager.ts +10 -0
- package/src/managers/mcpManager.ts +32 -6
- package/src/managers/permissionManager.ts +0 -42
- package/src/managers/slashCommandManager.ts +6 -0
- package/src/managers/toolManager.ts +47 -1
- package/src/prompts/index.ts +17 -3
- package/src/services/initializationService.ts +2 -41
- package/src/services/memory.ts +30 -17
- package/src/tools/cronCreateTool.ts +81 -8
- package/src/tools/cronDeleteTool.ts +7 -2
- package/src/tools/cronListTool.ts +7 -2
- package/src/tools/enterWorktreeTool.ts +183 -0
- package/src/tools/exitWorktreeTool.ts +242 -0
- package/src/tools/taskManagementTools.ts +4 -0
- package/src/tools/toolSearchTool.ts +228 -0
- package/src/tools/types.ts +19 -0
- package/src/tools/webFetchTool.ts +1 -0
- package/src/types/agent.ts +6 -0
- package/src/types/hooks.ts +4 -0
- package/src/utils/containerSetup.ts +7 -8
- package/src/utils/cronToHuman.ts +99 -0
- package/src/utils/isDeferredTool.ts +36 -0
- package/src/utils/mcpUtils.ts +1 -0
- package/src/utils/parseCronExpression.ts +78 -0
- package/src/utils/worktreeSession.ts +36 -0
- package/src/utils/worktreeUtils.ts +288 -0
|
@@ -1,25 +1,67 @@
|
|
|
1
1
|
import { CRON_CREATE_TOOL_NAME } from "../constants/tools.js";
|
|
2
|
+
import { cronToHuman } from "../utils/cronToHuman.js";
|
|
3
|
+
import { parseCronExpression } from "../utils/parseCronExpression.js";
|
|
4
|
+
const DEFAULT_MAX_AGE_DAYS = 7;
|
|
5
|
+
const MAX_JOBS = 50;
|
|
6
|
+
const CRON_CREATE_DESCRIPTION = `Schedule a prompt to run at a future time within this Wave session — either recurring on a cron schedule, or once at a specific time.`;
|
|
7
|
+
const CRON_CREATE_PROMPT = `Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders.
|
|
8
|
+
|
|
9
|
+
Uses standard 5-field cron in the user's local timezone: minute hour day-of-month month day-of-week. "0 9 * * *" means 9am local — no timezone conversion needed.
|
|
10
|
+
|
|
11
|
+
## One-shot tasks (recurring: false)
|
|
12
|
+
|
|
13
|
+
For "remind me at X" or "at <time>, do Y" requests — fire once then auto-delete.
|
|
14
|
+
Pin minute/hour/day-of-month/month to specific values:
|
|
15
|
+
"remind me at 2:30pm today to check the deploy" → cron: "30 14 <today_dom> <today_month> *", recurring: false
|
|
16
|
+
"tomorrow morning, run the smoke test" → cron: "57 8 <tomorrow_dom> <tomorrow_month> *", recurring: false
|
|
17
|
+
|
|
18
|
+
## Recurring jobs (recurring: true, the default)
|
|
19
|
+
|
|
20
|
+
For "every N minutes" / "every hour" / "weekdays at 9am" requests:
|
|
21
|
+
"*/5 * * * *" (every 5 min), "0 * * * *" (hourly), "0 9 * * 1-5" (weekdays at 9am local)
|
|
22
|
+
|
|
23
|
+
## Avoid the :00 and :30 minute marks when the task allows it
|
|
24
|
+
|
|
25
|
+
Every user who asks for "9am" gets \`0 9\`, and every user who asks for "hourly" gets \`0 *\` — which means requests from across the planet land on the API at the same instant. When the user's request is approximate, pick a minute that is NOT 0 or 30:
|
|
26
|
+
"every morning around 9" → "57 8 * * *" or "3 9 * * *" (not "0 9 * * *")
|
|
27
|
+
"hourly" → "7 * * * *" (not "0 * * * *")
|
|
28
|
+
"in an hour or so, remind me to..." → pick whatever minute you land on, don't round
|
|
29
|
+
|
|
30
|
+
Only use minute 0 or 30 when the user names that exact time and clearly means it ("at 9:00 sharp", "at half past", coordinating with a meeting). When in doubt, nudge a few minutes early or late — the user will not notice, and the fleet will.
|
|
31
|
+
|
|
32
|
+
## Session-only
|
|
33
|
+
|
|
34
|
+
Jobs live only in this Wave session — nothing is written to disk, and the job is gone when Wave exits.
|
|
35
|
+
|
|
36
|
+
## Runtime behavior
|
|
37
|
+
|
|
38
|
+
Jobs only fire while the REPL is idle (not mid-query). The scheduler adds a small deterministic jitter on top of whatever you pick: recurring tasks fire up to 10% of their period late (max 15 min); one-shot tasks landing on :00 or :30 fire up to 90s early. Picking an off-minute is still the bigger lever.
|
|
39
|
+
|
|
40
|
+
Recurring tasks auto-expire after ${DEFAULT_MAX_AGE_DAYS} days — they fire one final time, then are deleted. This bounds session lifetime. Tell the user about the ${DEFAULT_MAX_AGE_DAYS}-day limit when scheduling recurring jobs.
|
|
41
|
+
|
|
42
|
+
Returns a job ID you can pass to CronDelete.`;
|
|
2
43
|
export const cronCreateTool = {
|
|
3
44
|
name: CRON_CREATE_TOOL_NAME,
|
|
45
|
+
shouldDefer: true,
|
|
4
46
|
config: {
|
|
5
47
|
type: "function",
|
|
6
48
|
function: {
|
|
7
49
|
name: CRON_CREATE_TOOL_NAME,
|
|
8
|
-
description:
|
|
50
|
+
description: CRON_CREATE_DESCRIPTION,
|
|
9
51
|
parameters: {
|
|
10
52
|
type: "object",
|
|
11
53
|
properties: {
|
|
12
54
|
cron: {
|
|
13
55
|
type: "string",
|
|
14
|
-
description: 'Standard 5-field cron expression in local time: "M H DoM Mon DoW"',
|
|
56
|
+
description: 'Standard 5-field cron expression in local time: "M H DoM Mon DoW" (e.g. "*/5 * * * *" = every 5 minutes, "30 14 28 2 *" = Feb 28 at 2:30pm local once).',
|
|
15
57
|
},
|
|
16
58
|
prompt: {
|
|
17
59
|
type: "string",
|
|
18
|
-
description: "The prompt to enqueue at each fire time",
|
|
60
|
+
description: "The prompt to enqueue at each fire time.",
|
|
19
61
|
},
|
|
20
62
|
recurring: {
|
|
21
63
|
type: "boolean",
|
|
22
|
-
description:
|
|
64
|
+
description: `true (default) = fire on every cron match until deleted or auto-expired after ${DEFAULT_MAX_AGE_DAYS} days. false = fire once at the next match, then auto-delete. Use false for "remind me at X" one-shot requests with pinned minute/hour/dom/month.`,
|
|
23
65
|
default: true,
|
|
24
66
|
},
|
|
25
67
|
},
|
|
@@ -27,6 +69,7 @@ export const cronCreateTool = {
|
|
|
27
69
|
},
|
|
28
70
|
},
|
|
29
71
|
},
|
|
72
|
+
prompt: () => CRON_CREATE_PROMPT,
|
|
30
73
|
execute: async (args, context) => {
|
|
31
74
|
const { cron, prompt, recurring = true, } = args;
|
|
32
75
|
if (!context.cronManager) {
|
|
@@ -36,16 +79,38 @@ export const cronCreateTool = {
|
|
|
36
79
|
error: "CronManager not available",
|
|
37
80
|
};
|
|
38
81
|
}
|
|
82
|
+
// Validate cron expression
|
|
83
|
+
if (!parseCronExpression(cron)) {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
content: "",
|
|
87
|
+
error: `Invalid cron expression '${cron}'. Expected 5 fields: M H DoM Mon DoW.`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Check max jobs limit
|
|
91
|
+
const existingJobs = context.cronManager.listJobs();
|
|
92
|
+
if (existingJobs.length >= MAX_JOBS) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
content: "",
|
|
96
|
+
error: `Too many scheduled jobs (max ${MAX_JOBS}). Cancel one first.`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
39
99
|
try {
|
|
40
100
|
const job = context.cronManager.createJob({
|
|
41
101
|
cron,
|
|
42
102
|
prompt,
|
|
43
103
|
recurring,
|
|
44
104
|
});
|
|
105
|
+
const humanSchedule = cronToHuman(cron);
|
|
106
|
+
const where = "Session-only (not written to disk, dies when Wave exits)";
|
|
107
|
+
const resultMessage = recurring
|
|
108
|
+
? `Scheduled recurring job ${job.id} (${humanSchedule}). ${where}. Auto-expires after ${DEFAULT_MAX_AGE_DAYS} days. Use CronDelete to cancel sooner.`
|
|
109
|
+
: `Scheduled one-shot task ${job.id} (${humanSchedule}). ${where}. It will fire once then auto-delete.`;
|
|
45
110
|
return {
|
|
46
111
|
success: true,
|
|
47
|
-
content: JSON.stringify({ id: job.id }, null, 2),
|
|
48
|
-
shortResult:
|
|
112
|
+
content: JSON.stringify({ id: job.id, humanSchedule, recurring }, null, 2),
|
|
113
|
+
shortResult: resultMessage,
|
|
49
114
|
};
|
|
50
115
|
}
|
|
51
116
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cronDeleteTool.d.ts","sourceRoot":"","sources":["../../src/tools/cronDeleteTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"cronDeleteTool.d.ts","sourceRoot":"","sources":["../../src/tools/cronDeleteTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAOjE,eAAO,MAAM,cAAc,EAAE,UA4C5B,CAAC"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { CRON_DELETE_TOOL_NAME } from "../constants/tools.js";
|
|
2
|
+
const CRON_DELETE_DESCRIPTION = "Cancel a scheduled cron job by ID";
|
|
3
|
+
const CRON_DELETE_PROMPT = `Cancel a cron job previously scheduled with CronCreate. Removes it from the in-memory session store.`;
|
|
2
4
|
export const cronDeleteTool = {
|
|
3
5
|
name: CRON_DELETE_TOOL_NAME,
|
|
6
|
+
shouldDefer: true,
|
|
4
7
|
config: {
|
|
5
8
|
type: "function",
|
|
6
9
|
function: {
|
|
7
10
|
name: CRON_DELETE_TOOL_NAME,
|
|
8
|
-
description:
|
|
11
|
+
description: CRON_DELETE_DESCRIPTION,
|
|
9
12
|
parameters: {
|
|
10
13
|
type: "object",
|
|
11
14
|
properties: {
|
|
@@ -18,6 +21,7 @@ export const cronDeleteTool = {
|
|
|
18
21
|
},
|
|
19
22
|
},
|
|
20
23
|
},
|
|
24
|
+
prompt: () => CRON_DELETE_PROMPT,
|
|
21
25
|
execute: async (args, context) => {
|
|
22
26
|
const { id } = args;
|
|
23
27
|
if (!context.cronManager) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cronListTool.d.ts","sourceRoot":"","sources":["../../src/tools/cronListTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"cronListTool.d.ts","sourceRoot":"","sources":["../../src/tools/cronListTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAOjE,eAAO,MAAM,YAAY,EAAE,UAmC1B,CAAC"}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { CRON_LIST_TOOL_NAME } from "../constants/tools.js";
|
|
2
|
+
const CRON_LIST_DESCRIPTION = "List scheduled cron jobs";
|
|
3
|
+
const CRON_LIST_PROMPT = `List all cron jobs scheduled via CronCreate in this session.`;
|
|
2
4
|
export const cronListTool = {
|
|
3
5
|
name: CRON_LIST_TOOL_NAME,
|
|
6
|
+
shouldDefer: true,
|
|
4
7
|
config: {
|
|
5
8
|
type: "function",
|
|
6
9
|
function: {
|
|
7
10
|
name: CRON_LIST_TOOL_NAME,
|
|
8
|
-
description:
|
|
11
|
+
description: CRON_LIST_DESCRIPTION,
|
|
9
12
|
parameters: {
|
|
10
13
|
type: "object",
|
|
11
14
|
properties: {},
|
|
12
15
|
},
|
|
13
16
|
},
|
|
14
17
|
},
|
|
18
|
+
prompt: () => CRON_LIST_PROMPT,
|
|
15
19
|
execute: async (_args, context) => {
|
|
16
20
|
if (!context.cronManager) {
|
|
17
21
|
return {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EnterWorktree tool - creates an isolated git worktree and switches the session into it.
|
|
3
|
+
* Mirrors Claude Code's EnterWorktree tool behavior and prompt.
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolPlugin } from "./types.js";
|
|
6
|
+
export declare const ENTER_WORKTREE_TOOL_PROMPT = "Use this tool ONLY when the user explicitly asks to work in a worktree. This tool creates an isolated git worktree and switches the current session into it.\n\n## When to Use\n\n- The user explicitly says \"worktree\" (e.g., \"start a worktree\", \"work in a worktree\", \"create a worktree\", \"use a worktree\")\n\n## When NOT to Use\n\n- The user asks to create a branch, switch branches, or work on a different branch \u2014 use git commands instead\n- The user asks to fix a bug or work on a feature \u2014 use normal git workflow unless they specifically mention worktrees\n- Never use this tool unless the user explicitly mentions \"worktree\"\n\n## Requirements\n\n- Must be in a git repository\n- Must not already be in a worktree\n\n## Behavior\n\n- Creates a new git worktree inside `.wave/worktrees/` with a new branch based on HEAD\n- Switches the session's working directory to the new worktree\n- Use ExitWorktree to leave the worktree mid-session (keep or remove). On session exit, if still in the worktree, the user will be prompted to keep or remove it\n\n## Parameters\n\n- `name` (optional): A name for the worktree. Each \"/\"-separated segment may contain only letters, digits, dots, underscores, and dashes; max 64 chars total. A random name is generated if not provided.\n";
|
|
7
|
+
export declare const enterWorktreeTool: ToolPlugin;
|
|
8
|
+
//# sourceMappingURL=enterWorktreeTool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enterWorktreeTool.d.ts","sourceRoot":"","sources":["../../src/tools/enterWorktreeTool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAetE,eAAO,MAAM,0BAA0B,qxCA0BtC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,UAsI/B,CAAC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EnterWorktree tool - creates an isolated git worktree and switches the session into it.
|
|
3
|
+
* Mirrors Claude Code's EnterWorktree tool behavior and prompt.
|
|
4
|
+
*/
|
|
5
|
+
import { getCurrentWorktreeSession, setCurrentWorktreeSession, } from "../utils/worktreeSession.js";
|
|
6
|
+
import { createWorktree, validateWorktreeName, generateWorktreeName, } from "../utils/worktreeUtils.js";
|
|
7
|
+
import { getGitMainRepoRoot } from "../utils/gitUtils.js";
|
|
8
|
+
import { ENTER_WORKTREE_TOOL_NAME } from "../constants/tools.js";
|
|
9
|
+
import { logger } from "../utils/globalLogger.js";
|
|
10
|
+
export const ENTER_WORKTREE_TOOL_PROMPT = `Use this tool ONLY when the user explicitly asks to work in a worktree. This tool creates an isolated git worktree and switches the current session into it.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- The user explicitly says "worktree" (e.g., "start a worktree", "work in a worktree", "create a worktree", "use a worktree")
|
|
15
|
+
|
|
16
|
+
## When NOT to Use
|
|
17
|
+
|
|
18
|
+
- The user asks to create a branch, switch branches, or work on a different branch — use git commands instead
|
|
19
|
+
- The user asks to fix a bug or work on a feature — use normal git workflow unless they specifically mention worktrees
|
|
20
|
+
- Never use this tool unless the user explicitly mentions "worktree"
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
- Must be in a git repository
|
|
25
|
+
- Must not already be in a worktree
|
|
26
|
+
|
|
27
|
+
## Behavior
|
|
28
|
+
|
|
29
|
+
- Creates a new git worktree inside \`.wave/worktrees/\` with a new branch based on HEAD
|
|
30
|
+
- Switches the session's working directory to the new worktree
|
|
31
|
+
- Use ExitWorktree to leave the worktree mid-session (keep or remove). On session exit, if still in the worktree, the user will be prompted to keep or remove it
|
|
32
|
+
|
|
33
|
+
## Parameters
|
|
34
|
+
|
|
35
|
+
- \`name\` (optional): A name for the worktree. Each "/"-separated segment may contain only letters, digits, dots, underscores, and dashes; max 64 chars total. A random name is generated if not provided.
|
|
36
|
+
`;
|
|
37
|
+
export const enterWorktreeTool = {
|
|
38
|
+
name: ENTER_WORKTREE_TOOL_NAME,
|
|
39
|
+
shouldDefer: true,
|
|
40
|
+
config: {
|
|
41
|
+
type: "function",
|
|
42
|
+
function: {
|
|
43
|
+
name: ENTER_WORKTREE_TOOL_NAME,
|
|
44
|
+
description: ENTER_WORKTREE_TOOL_PROMPT,
|
|
45
|
+
parameters: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
name: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: 'Optional name for the worktree. Each "/"-separated segment may contain only letters, digits, dots, underscores, and dashes; max 64 chars total. A random name is generated if not provided.',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
prompt: () => ENTER_WORKTREE_TOOL_PROMPT,
|
|
57
|
+
async execute(args, context) {
|
|
58
|
+
// Validate not already in a worktree created by this session
|
|
59
|
+
if (getCurrentWorktreeSession()) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
content: "Already in a worktree session. Use ExitWorktree to leave before creating a new one.",
|
|
63
|
+
error: "Already in a worktree session",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const name = args.name || generateWorktreeName();
|
|
67
|
+
// Validate the worktree name
|
|
68
|
+
try {
|
|
69
|
+
validateWorktreeName(name);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
content: `Invalid worktree name: ${e.message}`,
|
|
75
|
+
error: `Invalid worktree name: ${e.message}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Resolve to main repo root so worktree creation works from within a subdirectory
|
|
79
|
+
const mainRepoRoot = getGitMainRepoRoot(context.workdir);
|
|
80
|
+
if (!mainRepoRoot) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
content: "Cannot create a worktree: not in a git repository. Configure WorktreeCreate and WorktreeRemove hooks in settings.json to use worktree isolation with other VCS systems.",
|
|
84
|
+
error: "Not in a git repository",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Create the worktree (captures originalHeadCommit internally)
|
|
88
|
+
const worktreeInfo = createWorktree(name, mainRepoRoot);
|
|
89
|
+
// Build session state
|
|
90
|
+
const session = {
|
|
91
|
+
originalCwd: context.workdir,
|
|
92
|
+
worktreePath: worktreeInfo.path,
|
|
93
|
+
worktreeBranch: worktreeInfo.branch,
|
|
94
|
+
worktreeName: worktreeInfo.name,
|
|
95
|
+
isNew: worktreeInfo.isNew,
|
|
96
|
+
repoRoot: worktreeInfo.repoRoot,
|
|
97
|
+
originalHeadCommit: worktreeInfo.originalHeadCommit,
|
|
98
|
+
};
|
|
99
|
+
// Set module-level session state
|
|
100
|
+
setCurrentWorktreeSession(session);
|
|
101
|
+
// Update CWD via AIManager
|
|
102
|
+
const aiManager = context.aiManager;
|
|
103
|
+
if (aiManager) {
|
|
104
|
+
aiManager.setWorkdir(worktreeInfo.path);
|
|
105
|
+
}
|
|
106
|
+
// Also update the container's Workdir entry
|
|
107
|
+
// (Container is not directly accessible from ToolContext, but AIManager.setWorkdir
|
|
108
|
+
// handles both its internal field and process.chdir)
|
|
109
|
+
// Trigger WorktreeCreate hook if worktree is new
|
|
110
|
+
let hookTriggered = false;
|
|
111
|
+
if (session.isNew && context.hookManager) {
|
|
112
|
+
try {
|
|
113
|
+
const hookResults = await context.hookManager.executeHooks("WorktreeCreate", {
|
|
114
|
+
event: "WorktreeCreate",
|
|
115
|
+
projectDir: worktreeInfo.path,
|
|
116
|
+
timestamp: new Date(),
|
|
117
|
+
sessionId: context.sessionId ?? "",
|
|
118
|
+
transcriptPath: context.messageManager?.getTranscriptPath() ?? "",
|
|
119
|
+
cwd: worktreeInfo.path,
|
|
120
|
+
worktreeName: worktreeInfo.name,
|
|
121
|
+
env: Object.fromEntries(Object.entries(process.env).filter((e) => e[1] !== undefined)),
|
|
122
|
+
});
|
|
123
|
+
if (context.messageManager) {
|
|
124
|
+
context.hookManager.processHookResults("WorktreeCreate", hookResults, context.messageManager);
|
|
125
|
+
}
|
|
126
|
+
hookTriggered = true;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// Non-blocking: log but don't fail the tool
|
|
130
|
+
logger?.warn("WorktreeCreate hooks execution failed:", error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const branchInfo = worktreeInfo.branch
|
|
134
|
+
? ` on branch ${worktreeInfo.branch}`
|
|
135
|
+
: "";
|
|
136
|
+
const hookInfo = hookTriggered
|
|
137
|
+
? " WorktreeCreate hooks were executed."
|
|
138
|
+
: "";
|
|
139
|
+
return {
|
|
140
|
+
success: true,
|
|
141
|
+
content: `Created worktree at ${worktreeInfo.path}${branchInfo}. The session is now working in the worktree. Use ExitWorktree to leave mid-session, or exit the session to be prompted.${hookInfo}`,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExitWorktree tool - exits a worktree session and returns to the original directory.
|
|
3
|
+
* Mirrors Claude Code's ExitWorktree tool behavior and prompt.
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolPlugin } from "./types.js";
|
|
6
|
+
export declare const EXIT_WORKTREE_TOOL_PROMPT = "Exit a worktree session created by EnterWorktree and return the session to the original working directory.\n\n## Scope\n\nThis tool ONLY operates on worktrees created by EnterWorktree in this session. It will NOT touch:\n- Worktrees you created manually with `git worktree add`\n- Worktrees from a previous session (even if created by EnterWorktree then)\n- The directory you're in if EnterWorktree was never called\n\nIf called outside an EnterWorktree session, the tool is a **no-op**: it reports that no worktree session is active and takes no action. Filesystem state is unchanged.\n\n## When to Use\n\n- The user explicitly asks to \"exit the worktree\", \"leave the worktree\", \"go back\", or otherwise end the worktree session\n- Do NOT call this proactively \u2014 only when the user asks\n\n## Parameters\n\n- `action` (required): `\"keep\"` or `\"remove\"`\n - `\"keep\"` \u2014 leave the worktree directory and branch intact on disk. Use this if the user wants to come back to the work later, or if there are changes to preserve.\n - `\"remove\"` \u2014 delete the worktree directory and its branch. Use this for a clean exit when the work is done or abandoned.\n- `discard_changes` (optional, default false): only meaningful with `action: \"remove\"`. If the worktree has uncommitted files or commits not on the original branch, the tool will REFUSE to remove it unless this is set to `true`. If the tool returns an error listing changes, confirm with the user before re-invoking with `discard_changes: true`.\n\n## Behavior\n\n- Restores the session's working directory to where it was before EnterWorktree\n- If action is \"remove\": deletes the worktree directory and branch\n- Once exited, EnterWorktree can be called again to create a fresh worktree\n";
|
|
7
|
+
export declare const exitWorktreeTool: ToolPlugin;
|
|
8
|
+
//# sourceMappingURL=exitWorktreeTool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exitWorktreeTool.d.ts","sourceRoot":"","sources":["../../src/tools/exitWorktreeTool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AAYtE,eAAO,MAAM,yBAAyB,gvDA4BrC,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,UAkM9B,CAAC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExitWorktree tool - exits a worktree session and returns to the original directory.
|
|
3
|
+
* Mirrors Claude Code's ExitWorktree tool behavior and prompt.
|
|
4
|
+
*/
|
|
5
|
+
import { getCurrentWorktreeSession, setCurrentWorktreeSession, } from "../utils/worktreeSession.js";
|
|
6
|
+
import { removeWorktree, countWorktreeChanges, } from "../utils/worktreeUtils.js";
|
|
7
|
+
import { EXIT_WORKTREE_TOOL_NAME } from "../constants/tools.js";
|
|
8
|
+
import { logger } from "../utils/globalLogger.js";
|
|
9
|
+
export const EXIT_WORKTREE_TOOL_PROMPT = `Exit a worktree session created by EnterWorktree and return the session to the original working directory.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
This tool ONLY operates on worktrees created by EnterWorktree in this session. It will NOT touch:
|
|
14
|
+
- Worktrees you created manually with \`git worktree add\`
|
|
15
|
+
- Worktrees from a previous session (even if created by EnterWorktree then)
|
|
16
|
+
- The directory you're in if EnterWorktree was never called
|
|
17
|
+
|
|
18
|
+
If called outside an EnterWorktree session, the tool is a **no-op**: it reports that no worktree session is active and takes no action. Filesystem state is unchanged.
|
|
19
|
+
|
|
20
|
+
## When to Use
|
|
21
|
+
|
|
22
|
+
- The user explicitly asks to "exit the worktree", "leave the worktree", "go back", or otherwise end the worktree session
|
|
23
|
+
- Do NOT call this proactively — only when the user asks
|
|
24
|
+
|
|
25
|
+
## Parameters
|
|
26
|
+
|
|
27
|
+
- \`action\` (required): \`"keep"\` or \`"remove"\`
|
|
28
|
+
- \`"keep"\` — leave the worktree directory and branch intact on disk. Use this if the user wants to come back to the work later, or if there are changes to preserve.
|
|
29
|
+
- \`"remove"\` — delete the worktree directory and its branch. Use this for a clean exit when the work is done or abandoned.
|
|
30
|
+
- \`discard_changes\` (optional, default false): only meaningful with \`action: "remove"\`. If the worktree has uncommitted files or commits not on the original branch, the tool will REFUSE to remove it unless this is set to \`true\`. If the tool returns an error listing changes, confirm with the user before re-invoking with \`discard_changes: true\`.
|
|
31
|
+
|
|
32
|
+
## Behavior
|
|
33
|
+
|
|
34
|
+
- Restores the session's working directory to where it was before EnterWorktree
|
|
35
|
+
- If action is "remove": deletes the worktree directory and branch
|
|
36
|
+
- Once exited, EnterWorktree can be called again to create a fresh worktree
|
|
37
|
+
`;
|
|
38
|
+
export const exitWorktreeTool = {
|
|
39
|
+
name: EXIT_WORKTREE_TOOL_NAME,
|
|
40
|
+
shouldDefer: true,
|
|
41
|
+
config: {
|
|
42
|
+
type: "function",
|
|
43
|
+
function: {
|
|
44
|
+
name: EXIT_WORKTREE_TOOL_NAME,
|
|
45
|
+
description: EXIT_WORKTREE_TOOL_PROMPT,
|
|
46
|
+
parameters: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
action: {
|
|
50
|
+
type: "string",
|
|
51
|
+
enum: ["keep", "remove"],
|
|
52
|
+
description: '"keep" leaves the worktree and branch on disk; "remove" deletes both.',
|
|
53
|
+
},
|
|
54
|
+
discard_changes: {
|
|
55
|
+
type: "boolean",
|
|
56
|
+
description: 'Required true when action is "remove" and the worktree has uncommitted files or unmerged commits. The tool will refuse and list them otherwise.',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["action"],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
prompt: () => EXIT_WORKTREE_TOOL_PROMPT,
|
|
64
|
+
async execute(args, context) {
|
|
65
|
+
const action = args.action;
|
|
66
|
+
const discardChanges = args.discard_changes ?? false;
|
|
67
|
+
if (!action) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
content: 'Missing required parameter: "action" must be "keep" or "remove".',
|
|
71
|
+
error: 'Missing required parameter: "action"',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Validate: must be in an active worktree session
|
|
75
|
+
const session = getCurrentWorktreeSession();
|
|
76
|
+
if (!session) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
content: "No-op: there is no active EnterWorktree session to exit. This tool only operates on worktrees created by EnterWorktree in the current session — it will not touch worktrees created manually or in a previous session. No filesystem changes were made.",
|
|
80
|
+
error: "No active worktree session",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// Safety check for removal with changes
|
|
84
|
+
if (action === "remove" && !discardChanges) {
|
|
85
|
+
const summary = countWorktreeChanges(session.worktreePath, session.originalHeadCommit);
|
|
86
|
+
if (summary === null) {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
content: `Could not verify worktree state at ${session.worktreePath}. Refusing to remove without explicit confirmation. Re-invoke with discard_changes: true to proceed — or use action: "keep" to preserve the worktree.`,
|
|
90
|
+
error: "Could not verify worktree state",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const { changedFiles, commits } = summary;
|
|
94
|
+
if (changedFiles > 0 || commits > 0) {
|
|
95
|
+
const parts = [];
|
|
96
|
+
if (changedFiles > 0) {
|
|
97
|
+
parts.push(`${changedFiles} uncommitted ${changedFiles === 1 ? "file" : "files"}`);
|
|
98
|
+
}
|
|
99
|
+
if (commits > 0) {
|
|
100
|
+
parts.push(`${commits} ${commits === 1 ? "commit" : "commits"} on ${session.worktreeBranch ?? "the worktree branch"}`);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
content: `Worktree has ${parts.join(" and ")}. Removing will discard this work permanently. Confirm with the user, then re-invoke with discard_changes: true — or use action: "keep" to preserve the worktree.`,
|
|
105
|
+
error: "Worktree has uncommitted changes",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Capture info before clearing session
|
|
110
|
+
const { originalCwd, worktreePath, worktreeBranch } = session;
|
|
111
|
+
if (action === "keep") {
|
|
112
|
+
// Clear session state
|
|
113
|
+
setCurrentWorktreeSession(null);
|
|
114
|
+
// Restore CWD
|
|
115
|
+
const aiManager = context.aiManager;
|
|
116
|
+
if (aiManager) {
|
|
117
|
+
aiManager.setWorkdir(originalCwd);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
content: `Exited worktree. Your work is preserved at ${worktreePath}${worktreeBranch ? ` on branch ${worktreeBranch}` : ""}. Session is now back in ${originalCwd}.`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// action === "remove"
|
|
125
|
+
const worktreeInfo = {
|
|
126
|
+
name: session.worktreeName,
|
|
127
|
+
path: worktreePath,
|
|
128
|
+
branch: worktreeBranch,
|
|
129
|
+
repoRoot: session.repoRoot,
|
|
130
|
+
isNew: session.isNew,
|
|
131
|
+
};
|
|
132
|
+
// Count changes BEFORE removing the worktree (directory will be gone after)
|
|
133
|
+
const summary = countWorktreeChanges(worktreePath, session.originalHeadCommit) ?? { changedFiles: 0, commits: 0 };
|
|
134
|
+
removeWorktree(worktreeInfo);
|
|
135
|
+
// Clear session state
|
|
136
|
+
setCurrentWorktreeSession(null);
|
|
137
|
+
// Restore CWD
|
|
138
|
+
const aiManager = context.aiManager;
|
|
139
|
+
if (aiManager) {
|
|
140
|
+
aiManager.setWorkdir(originalCwd);
|
|
141
|
+
}
|
|
142
|
+
// Trigger WorktreeRemove hook (non-blocking)
|
|
143
|
+
let hookTriggered = false;
|
|
144
|
+
if (context.hookManager) {
|
|
145
|
+
try {
|
|
146
|
+
const hookResults = await context.hookManager.executeHooks("WorktreeRemove", {
|
|
147
|
+
event: "WorktreeRemove",
|
|
148
|
+
projectDir: originalCwd,
|
|
149
|
+
timestamp: new Date(),
|
|
150
|
+
sessionId: context.sessionId ?? "",
|
|
151
|
+
transcriptPath: context.messageManager?.getTranscriptPath() ?? "",
|
|
152
|
+
cwd: originalCwd,
|
|
153
|
+
worktreePath,
|
|
154
|
+
env: Object.fromEntries(Object.entries(process.env).filter((e) => e[1] !== undefined)),
|
|
155
|
+
});
|
|
156
|
+
if (context.messageManager) {
|
|
157
|
+
context.hookManager.processHookResults("WorktreeRemove", hookResults, context.messageManager);
|
|
158
|
+
}
|
|
159
|
+
hookTriggered = true;
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
// Non-blocking: log but don't fail the tool
|
|
163
|
+
logger?.warn("WorktreeRemove hooks execution failed:", error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const discardParts = [];
|
|
167
|
+
if (summary.commits > 0) {
|
|
168
|
+
discardParts.push(`${summary.commits} ${summary.commits === 1 ? "commit" : "commits"}`);
|
|
169
|
+
}
|
|
170
|
+
if (summary.changedFiles > 0) {
|
|
171
|
+
discardParts.push(`${summary.changedFiles} uncommitted ${summary.changedFiles === 1 ? "file" : "files"}`);
|
|
172
|
+
}
|
|
173
|
+
const discardNote = discardParts.length > 0
|
|
174
|
+
? ` Discarded ${discardParts.join(" and ")}.`
|
|
175
|
+
: "";
|
|
176
|
+
const hookNote = hookTriggered
|
|
177
|
+
? " WorktreeRemove hooks were executed."
|
|
178
|
+
: "";
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
content: `Exited and removed worktree at ${worktreePath}.${discardNote} Session is now back in ${originalCwd}.${hookNote}`,
|
|
182
|
+
};
|
|
183
|
+
},
|
|
184
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taskManagementTools.d.ts","sourceRoot":"","sources":["../../src/tools/taskManagementTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AASjE,eAAO,MAAM,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"taskManagementTools.d.ts","sourceRoot":"","sources":["../../src/tools/taskManagementTools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AASjE,eAAO,MAAM,cAAc,EAAE,UAqI5B,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,UA0DzB,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,UAiW5B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,UAmE1B,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TASK_CREATE_TOOL_NAME, TASK_GET_TOOL_NAME, TASK_UPDATE_TOOL_NAME, TASK_LIST_TOOL_NAME, } from "../constants/tools.js";
|
|
2
2
|
export const taskCreateTool = {
|
|
3
3
|
name: TASK_CREATE_TOOL_NAME,
|
|
4
|
+
shouldDefer: true,
|
|
4
5
|
config: {
|
|
5
6
|
type: "function",
|
|
6
7
|
function: {
|
|
@@ -123,6 +124,7 @@ NOTE that you should not use this tool if there is only one trivial task to do.
|
|
|
123
124
|
};
|
|
124
125
|
export const taskGetTool = {
|
|
125
126
|
name: TASK_GET_TOOL_NAME,
|
|
127
|
+
shouldDefer: true,
|
|
126
128
|
config: {
|
|
127
129
|
type: "function",
|
|
128
130
|
function: {
|
|
@@ -179,6 +181,7 @@ Returns full task details:
|
|
|
179
181
|
};
|
|
180
182
|
export const taskUpdateTool = {
|
|
181
183
|
name: TASK_UPDATE_TOOL_NAME,
|
|
184
|
+
shouldDefer: true,
|
|
182
185
|
config: {
|
|
183
186
|
type: "function",
|
|
184
187
|
function: {
|
|
@@ -478,6 +481,7 @@ Set up task dependencies:
|
|
|
478
481
|
};
|
|
479
482
|
export const taskListTool = {
|
|
480
483
|
name: TASK_LIST_TOOL_NAME,
|
|
484
|
+
shouldDefer: true,
|
|
481
485
|
config: {
|
|
482
486
|
type: "function",
|
|
483
487
|
function: {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolSearchTool - Discovers deferred tool schemas on demand.
|
|
3
|
+
*
|
|
4
|
+
* When tool deferral is enabled, deferred tools are not sent to the API.
|
|
5
|
+
* The model must call this tool to discover a deferred tool's full schema
|
|
6
|
+
* before it can invoke it.
|
|
7
|
+
*
|
|
8
|
+
* Query formats:
|
|
9
|
+
* - "select:ToolName" — direct selection by name (comma-separated for multiple)
|
|
10
|
+
* - "notebook jupyter" — keyword search, up to max_results best matches
|
|
11
|
+
* - "+slack send" — require "slack" in the name, rank by remaining terms
|
|
12
|
+
*/
|
|
13
|
+
import { ToolPlugin } from "./types.js";
|
|
14
|
+
export declare const toolSearchTool: ToolPlugin;
|
|
15
|
+
//# sourceMappingURL=toolSearchTool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolSearchTool.d.ts","sourceRoot":"","sources":["../../src/tools/toolSearchTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAA2B,MAAM,YAAY,CAAC;AA4GjE,eAAO,MAAM,cAAc,EAAE,UAsG5B,CAAC"}
|