@dexto/tools-plan 1.5.8 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -29
- package/dist/plan-service-getter.d.ts +4 -0
- package/dist/plan-service-getter.d.ts.map +1 -0
- package/dist/plan-service-getter.js +1 -0
- package/dist/plan-service.d.ts +2 -2
- package/dist/plan-service.d.ts.map +1 -1
- package/dist/plan-service.js +7 -7
- package/dist/plan-service.test.js +6 -0
- package/dist/tool-factory-config.d.ts +32 -0
- package/dist/tool-factory-config.d.ts.map +1 -0
- package/dist/tool-factory-config.js +30 -0
- package/dist/tool-factory.d.ts +4 -0
- package/dist/tool-factory.d.ts.map +1 -0
- package/dist/tool-factory.js +36 -0
- package/dist/tool-factory.test.d.ts +7 -0
- package/dist/tool-factory.test.d.ts.map +1 -0
- package/dist/tool-factory.test.js +100 -0
- package/dist/tools/plan-create-tool.d.ts +15 -3
- package/dist/tools/plan-create-tool.d.ts.map +1 -1
- package/dist/tools/plan-create-tool.js +29 -18
- package/dist/tools/plan-create-tool.test.js +47 -22
- package/dist/tools/plan-read-tool.d.ts +6 -3
- package/dist/tools/plan-read-tool.d.ts.map +1 -1
- package/dist/tools/plan-read-tool.js +10 -7
- package/dist/tools/plan-read-tool.test.js +31 -19
- package/dist/tools/plan-review-tool.d.ts +14 -5
- package/dist/tools/plan-review-tool.d.ts.map +1 -1
- package/dist/tools/plan-review-tool.js +16 -12
- package/dist/tools/plan-update-tool.d.ts +12 -3
- package/dist/tools/plan-update-tool.d.ts.map +1 -1
- package/dist/tools/plan-update-tool.js +14 -10
- package/dist/tools/plan-update-tool.test.js +40 -28
- package/package.json +4 -5
- package/.dexto-plugin/plugin.json +0 -7
- package/dist/tool-provider.d.ts +0 -44
- package/dist/tool-provider.d.ts.map +0 -1
- package/dist/tool-provider.js +0 -81
- package/dist/tool-provider.test.d.ts +0 -7
- package/dist/tool-provider.test.d.ts.map +0 -1
- package/dist/tool-provider.test.js +0 -185
- package/skills/plan/SKILL.md +0 -102
|
@@ -12,23 +12,35 @@ import { PlanService } from '../plan-service.js';
|
|
|
12
12
|
import { PlanErrorCode } from '../errors.js';
|
|
13
13
|
import { DextoRuntimeError } from '@dexto/core';
|
|
14
14
|
// Create mock logger
|
|
15
|
-
const createMockLogger = () =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
const createMockLogger = () => {
|
|
16
|
+
const logger = {
|
|
17
|
+
debug: vi.fn(),
|
|
18
|
+
silly: vi.fn(),
|
|
19
|
+
info: vi.fn(),
|
|
20
|
+
warn: vi.fn(),
|
|
21
|
+
error: vi.fn(),
|
|
22
|
+
trackException: vi.fn(),
|
|
23
|
+
createChild: vi.fn(() => logger),
|
|
24
|
+
setLevel: vi.fn(),
|
|
25
|
+
getLevel: vi.fn(() => 'debug'),
|
|
26
|
+
getLogFilePath: vi.fn(() => null),
|
|
27
|
+
destroy: vi.fn(async () => undefined),
|
|
28
|
+
};
|
|
29
|
+
return logger;
|
|
30
|
+
};
|
|
31
|
+
function createToolContext(logger, overrides = {}) {
|
|
32
|
+
return { logger, ...overrides };
|
|
33
|
+
}
|
|
22
34
|
describe('plan_create tool', () => {
|
|
23
|
-
let
|
|
35
|
+
let logger;
|
|
24
36
|
let tempDir;
|
|
25
37
|
let planService;
|
|
26
38
|
beforeEach(async () => {
|
|
27
|
-
|
|
39
|
+
logger = createMockLogger();
|
|
28
40
|
// Create temp directory for testing
|
|
29
41
|
const rawTempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dexto-plan-create-test-'));
|
|
30
42
|
tempDir = await fs.realpath(rawTempDir);
|
|
31
|
-
planService = new PlanService({ basePath: tempDir },
|
|
43
|
+
planService = new PlanService({ basePath: tempDir }, logger);
|
|
32
44
|
vi.clearAllMocks();
|
|
33
45
|
});
|
|
34
46
|
afterEach(async () => {
|
|
@@ -41,10 +53,10 @@ describe('plan_create tool', () => {
|
|
|
41
53
|
});
|
|
42
54
|
describe('generatePreview', () => {
|
|
43
55
|
it('should return FileDisplayData for new plan', async () => {
|
|
44
|
-
const tool = createPlanCreateTool(planService);
|
|
56
|
+
const tool = createPlanCreateTool(async () => planService);
|
|
45
57
|
const sessionId = 'test-session';
|
|
46
58
|
const content = '# Implementation Plan\n\n## Steps\n1. First step';
|
|
47
|
-
const preview = (await tool.generatePreview({ title: 'Test Plan', content }, { sessionId }));
|
|
59
|
+
const preview = (await tool.generatePreview({ title: 'Test Plan', content }, createToolContext(logger, { sessionId })));
|
|
48
60
|
expect(preview.type).toBe('file');
|
|
49
61
|
expect(preview.operation).toBe('create');
|
|
50
62
|
// Path is now absolute, check it ends with the expected suffix
|
|
@@ -54,9 +66,9 @@ describe('plan_create tool', () => {
|
|
|
54
66
|
expect(preview.lineCount).toBe(4);
|
|
55
67
|
});
|
|
56
68
|
it('should throw error when sessionId is missing', async () => {
|
|
57
|
-
const tool = createPlanCreateTool(planService);
|
|
69
|
+
const tool = createPlanCreateTool(async () => planService);
|
|
58
70
|
try {
|
|
59
|
-
await tool.generatePreview({ title: 'Test', content: '# Plan' },
|
|
71
|
+
await tool.generatePreview({ title: 'Test', content: '# Plan' }, createToolContext(logger));
|
|
60
72
|
expect.fail('Should have thrown an error');
|
|
61
73
|
}
|
|
62
74
|
catch (error) {
|
|
@@ -65,12 +77,12 @@ describe('plan_create tool', () => {
|
|
|
65
77
|
}
|
|
66
78
|
});
|
|
67
79
|
it('should throw error when plan already exists', async () => {
|
|
68
|
-
const tool = createPlanCreateTool(planService);
|
|
80
|
+
const tool = createPlanCreateTool(async () => planService);
|
|
69
81
|
const sessionId = 'test-session';
|
|
70
82
|
// Create existing plan
|
|
71
83
|
await planService.create(sessionId, '# Existing Plan');
|
|
72
84
|
try {
|
|
73
|
-
await tool.generatePreview({ title: 'New Plan', content: '# New Content' }, { sessionId });
|
|
85
|
+
await tool.generatePreview({ title: 'New Plan', content: '# New Content' }, createToolContext(logger, { sessionId }));
|
|
74
86
|
expect.fail('Should have thrown an error');
|
|
75
87
|
}
|
|
76
88
|
catch (error) {
|
|
@@ -81,11 +93,11 @@ describe('plan_create tool', () => {
|
|
|
81
93
|
});
|
|
82
94
|
describe('execute', () => {
|
|
83
95
|
it('should create plan and return success', async () => {
|
|
84
|
-
const tool = createPlanCreateTool(planService);
|
|
96
|
+
const tool = createPlanCreateTool(async () => planService);
|
|
85
97
|
const sessionId = 'test-session';
|
|
86
98
|
const content = '# Implementation Plan';
|
|
87
99
|
const title = 'My Plan';
|
|
88
|
-
const result = (await tool.execute({ title, content }, { sessionId }));
|
|
100
|
+
const result = (await tool.execute({ title, content }, createToolContext(logger, { sessionId })));
|
|
89
101
|
expect(result.success).toBe(true);
|
|
90
102
|
// Path is now absolute, check it ends with the expected suffix
|
|
91
103
|
expect(result.path).toContain(sessionId);
|
|
@@ -93,10 +105,23 @@ describe('plan_create tool', () => {
|
|
|
93
105
|
expect(result.status).toBe('draft');
|
|
94
106
|
expect(result.title).toBe(title);
|
|
95
107
|
});
|
|
108
|
+
it('should throw error when plan already exists', async () => {
|
|
109
|
+
const tool = createPlanCreateTool(async () => planService);
|
|
110
|
+
const sessionId = 'test-session';
|
|
111
|
+
await planService.create(sessionId, '# Existing Plan');
|
|
112
|
+
try {
|
|
113
|
+
await tool.execute({ title: 'New Plan', content: '# New content' }, createToolContext(logger, { sessionId }));
|
|
114
|
+
expect.fail('Should have thrown an error');
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
expect(error).toBeInstanceOf(DextoRuntimeError);
|
|
118
|
+
expect(error.code).toBe(PlanErrorCode.PLAN_ALREADY_EXISTS);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
96
121
|
it('should throw error when sessionId is missing', async () => {
|
|
97
|
-
const tool = createPlanCreateTool(planService);
|
|
122
|
+
const tool = createPlanCreateTool(async () => planService);
|
|
98
123
|
try {
|
|
99
|
-
await tool.execute({ title: 'Test', content: '# Plan' },
|
|
124
|
+
await tool.execute({ title: 'Test', content: '# Plan' }, createToolContext(logger));
|
|
100
125
|
expect.fail('Should have thrown an error');
|
|
101
126
|
}
|
|
102
127
|
catch (error) {
|
|
@@ -105,10 +130,10 @@ describe('plan_create tool', () => {
|
|
|
105
130
|
}
|
|
106
131
|
});
|
|
107
132
|
it('should include _display data in result', async () => {
|
|
108
|
-
const tool = createPlanCreateTool(planService);
|
|
133
|
+
const tool = createPlanCreateTool(async () => planService);
|
|
109
134
|
const sessionId = 'test-session';
|
|
110
135
|
const content = '# Plan\n## Steps';
|
|
111
|
-
const result = (await tool.execute({ title: 'Plan', content }, { sessionId }));
|
|
136
|
+
const result = (await tool.execute({ title: 'Plan', content }, createToolContext(logger, { sessionId })));
|
|
112
137
|
expect(result._display).toBeDefined();
|
|
113
138
|
expect(result._display.type).toBe('file');
|
|
114
139
|
expect(result._display.operation).toBe('create');
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* Reads the current implementation plan for the session.
|
|
5
5
|
* No approval needed - read-only operation.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
8
|
-
import type {
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { Tool } from '@dexto/core';
|
|
9
|
+
import type { PlanServiceGetter } from '../plan-service-getter.js';
|
|
10
|
+
declare const PlanReadInputSchema: z.ZodObject<{}, "strict", z.ZodTypeAny, {}, {}>;
|
|
9
11
|
/**
|
|
10
12
|
* Creates the plan_read tool
|
|
11
13
|
*/
|
|
12
|
-
export declare function createPlanReadTool(
|
|
14
|
+
export declare function createPlanReadTool(getPlanService: PlanServiceGetter): Tool<typeof PlanReadInputSchema>;
|
|
15
|
+
export {};
|
|
13
16
|
//# sourceMappingURL=plan-read-tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-read-tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-read-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"plan-read-tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-read-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,IAAI,EAAwB,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAGnE,QAAA,MAAM,mBAAmB,iDAAwB,CAAC;AAElD;;GAEG;AACH,wBAAgB,kBAAkB,CAC9B,cAAc,EAAE,iBAAiB,GAClC,IAAI,CAAC,OAAO,mBAAmB,CAAC,CAkClC"}
|
|
@@ -5,21 +5,24 @@
|
|
|
5
5
|
* No approval needed - read-only operation.
|
|
6
6
|
*/
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
+
import { defineTool } from '@dexto/core';
|
|
8
9
|
import { PlanError } from '../errors.js';
|
|
9
10
|
const PlanReadInputSchema = z.object({}).strict();
|
|
10
11
|
/**
|
|
11
12
|
* Creates the plan_read tool
|
|
12
13
|
*/
|
|
13
|
-
export function createPlanReadTool(
|
|
14
|
-
return {
|
|
14
|
+
export function createPlanReadTool(getPlanService) {
|
|
15
|
+
return defineTool({
|
|
15
16
|
id: 'plan_read',
|
|
17
|
+
displayName: 'Read Plan',
|
|
16
18
|
description: 'Read the current implementation plan for this session. Returns the plan content and metadata including status. Use markdown checkboxes (- [ ] and - [x]) in the content to track progress.',
|
|
17
19
|
inputSchema: PlanReadInputSchema,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
async execute(_input, context) {
|
|
21
|
+
const resolvedPlanService = await getPlanService(context);
|
|
22
|
+
if (!context.sessionId) {
|
|
20
23
|
throw PlanError.sessionIdRequired();
|
|
21
24
|
}
|
|
22
|
-
const plan = await
|
|
25
|
+
const plan = await resolvedPlanService.read(context.sessionId);
|
|
23
26
|
if (!plan) {
|
|
24
27
|
return {
|
|
25
28
|
exists: false,
|
|
@@ -28,7 +31,7 @@ export function createPlanReadTool(planService) {
|
|
|
28
31
|
}
|
|
29
32
|
return {
|
|
30
33
|
exists: true,
|
|
31
|
-
path:
|
|
34
|
+
path: resolvedPlanService.getPlanPath(context.sessionId),
|
|
32
35
|
content: plan.content,
|
|
33
36
|
status: plan.meta.status,
|
|
34
37
|
title: plan.meta.title,
|
|
@@ -36,5 +39,5 @@ export function createPlanReadTool(planService) {
|
|
|
36
39
|
updatedAt: new Date(plan.meta.updatedAt).toISOString(),
|
|
37
40
|
};
|
|
38
41
|
},
|
|
39
|
-
};
|
|
42
|
+
});
|
|
40
43
|
}
|
|
@@ -12,22 +12,34 @@ import { PlanService } from '../plan-service.js';
|
|
|
12
12
|
import { PlanErrorCode } from '../errors.js';
|
|
13
13
|
import { DextoRuntimeError } from '@dexto/core';
|
|
14
14
|
// Create mock logger
|
|
15
|
-
const createMockLogger = () =>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
const createMockLogger = () => {
|
|
16
|
+
const logger = {
|
|
17
|
+
debug: vi.fn(),
|
|
18
|
+
silly: vi.fn(),
|
|
19
|
+
info: vi.fn(),
|
|
20
|
+
warn: vi.fn(),
|
|
21
|
+
error: vi.fn(),
|
|
22
|
+
trackException: vi.fn(),
|
|
23
|
+
createChild: vi.fn(() => logger),
|
|
24
|
+
setLevel: vi.fn(),
|
|
25
|
+
getLevel: vi.fn(() => 'debug'),
|
|
26
|
+
getLogFilePath: vi.fn(() => null),
|
|
27
|
+
destroy: vi.fn(async () => undefined),
|
|
28
|
+
};
|
|
29
|
+
return logger;
|
|
30
|
+
};
|
|
31
|
+
function createToolContext(logger, overrides = {}) {
|
|
32
|
+
return { logger, ...overrides };
|
|
33
|
+
}
|
|
22
34
|
describe('plan_read tool', () => {
|
|
23
|
-
let
|
|
35
|
+
let logger;
|
|
24
36
|
let tempDir;
|
|
25
37
|
let planService;
|
|
26
38
|
beforeEach(async () => {
|
|
27
|
-
|
|
39
|
+
logger = createMockLogger();
|
|
28
40
|
const rawTempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dexto-plan-read-test-'));
|
|
29
41
|
tempDir = await fs.realpath(rawTempDir);
|
|
30
|
-
planService = new PlanService({ basePath: tempDir },
|
|
42
|
+
planService = new PlanService({ basePath: tempDir }, logger);
|
|
31
43
|
vi.clearAllMocks();
|
|
32
44
|
});
|
|
33
45
|
afterEach(async () => {
|
|
@@ -40,38 +52,38 @@ describe('plan_read tool', () => {
|
|
|
40
52
|
});
|
|
41
53
|
describe('execute', () => {
|
|
42
54
|
it('should return exists: false when no plan exists', async () => {
|
|
43
|
-
const tool = createPlanReadTool(planService);
|
|
55
|
+
const tool = createPlanReadTool(async () => planService);
|
|
44
56
|
const sessionId = 'test-session';
|
|
45
|
-
const result = (await tool.execute({}, { sessionId }));
|
|
57
|
+
const result = (await tool.execute({}, createToolContext(logger, { sessionId })));
|
|
46
58
|
expect(result.exists).toBe(false);
|
|
47
59
|
expect(result.message).toContain('No plan found');
|
|
48
60
|
});
|
|
49
61
|
it('should return plan content and metadata when plan exists', async () => {
|
|
50
|
-
const tool = createPlanReadTool(planService);
|
|
62
|
+
const tool = createPlanReadTool(async () => planService);
|
|
51
63
|
const sessionId = 'test-session';
|
|
52
64
|
const content = '# My Plan\n\nSome content';
|
|
53
65
|
const title = 'My Plan Title';
|
|
54
66
|
await planService.create(sessionId, content, { title });
|
|
55
|
-
const result = (await tool.execute({}, { sessionId }));
|
|
67
|
+
const result = (await tool.execute({}, createToolContext(logger, { sessionId })));
|
|
56
68
|
expect(result.exists).toBe(true);
|
|
57
69
|
expect(result.content).toBe(content);
|
|
58
70
|
expect(result.status).toBe('draft');
|
|
59
71
|
expect(result.title).toBe(title);
|
|
60
|
-
expect(result.path).toBe(
|
|
72
|
+
expect(result.path).toBe(path.join(tempDir, sessionId, 'plan.md'));
|
|
61
73
|
});
|
|
62
74
|
it('should return ISO timestamps', async () => {
|
|
63
|
-
const tool = createPlanReadTool(planService);
|
|
75
|
+
const tool = createPlanReadTool(async () => planService);
|
|
64
76
|
const sessionId = 'test-session';
|
|
65
77
|
await planService.create(sessionId, '# Plan');
|
|
66
|
-
const result = (await tool.execute({}, { sessionId }));
|
|
78
|
+
const result = (await tool.execute({}, createToolContext(logger, { sessionId })));
|
|
67
79
|
// Should be ISO format
|
|
68
80
|
expect(result.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
69
81
|
expect(result.updatedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
|
|
70
82
|
});
|
|
71
83
|
it('should throw error when sessionId is missing', async () => {
|
|
72
|
-
const tool = createPlanReadTool(planService);
|
|
84
|
+
const tool = createPlanReadTool(async () => planService);
|
|
73
85
|
try {
|
|
74
|
-
await tool.execute({},
|
|
86
|
+
await tool.execute({}, createToolContext(logger));
|
|
75
87
|
expect.fail('Should have thrown an error');
|
|
76
88
|
}
|
|
77
89
|
catch (error) {
|
|
@@ -8,15 +8,24 @@
|
|
|
8
8
|
* - Request Changes: Provide feedback for iteration
|
|
9
9
|
* - Reject: Reject the plan entirely
|
|
10
10
|
*
|
|
11
|
-
* Uses the tool
|
|
11
|
+
* Uses the tool approval pattern (not elicitation) so the user
|
|
12
12
|
* can see the full plan content before deciding.
|
|
13
13
|
*/
|
|
14
|
-
import
|
|
15
|
-
import type {
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import type { Tool } from '@dexto/core';
|
|
16
|
+
import type { PlanServiceGetter } from '../plan-service-getter.js';
|
|
17
|
+
declare const PlanReviewInputSchema: z.ZodObject<{
|
|
18
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
19
|
+
}, "strict", z.ZodTypeAny, {
|
|
20
|
+
summary?: string | undefined;
|
|
21
|
+
}, {
|
|
22
|
+
summary?: string | undefined;
|
|
23
|
+
}>;
|
|
16
24
|
/**
|
|
17
25
|
* Creates the plan_review tool
|
|
18
26
|
*
|
|
19
|
-
* @param
|
|
27
|
+
* @param getPlanService - Getter for the plan service
|
|
20
28
|
*/
|
|
21
|
-
export declare function createPlanReviewTool(
|
|
29
|
+
export declare function createPlanReviewTool(getPlanService: PlanServiceGetter): Tool<typeof PlanReviewInputSchema>;
|
|
30
|
+
export {};
|
|
22
31
|
//# sourceMappingURL=plan-review-tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-review-tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-review-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"plan-review-tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-review-tool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,IAAI,EAAyC,MAAM,aAAa,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAGnE,QAAA,MAAM,qBAAqB;;;;;;EAOd,CAAC;AAEd;;;;GAIG;AACH,wBAAgB,oBAAoB,CAChC,cAAc,EAAE,iBAAiB,GAClC,IAAI,CAAC,OAAO,qBAAqB,CAAC,CAoEpC"}
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* - Request Changes: Provide feedback for iteration
|
|
9
9
|
* - Reject: Reject the plan entirely
|
|
10
10
|
*
|
|
11
|
-
* Uses the tool
|
|
11
|
+
* Uses the tool approval pattern (not elicitation) so the user
|
|
12
12
|
* can see the full plan content before deciding.
|
|
13
13
|
*/
|
|
14
14
|
import { z } from 'zod';
|
|
15
|
+
import { defineTool } from '@dexto/core';
|
|
15
16
|
import { PlanError } from '../errors.js';
|
|
16
17
|
const PlanReviewInputSchema = z
|
|
17
18
|
.object({
|
|
@@ -24,11 +25,12 @@ const PlanReviewInputSchema = z
|
|
|
24
25
|
/**
|
|
25
26
|
* Creates the plan_review tool
|
|
26
27
|
*
|
|
27
|
-
* @param
|
|
28
|
+
* @param getPlanService - Getter for the plan service
|
|
28
29
|
*/
|
|
29
|
-
export function createPlanReviewTool(
|
|
30
|
-
return {
|
|
30
|
+
export function createPlanReviewTool(getPlanService) {
|
|
31
|
+
return defineTool({
|
|
31
32
|
id: 'plan_review',
|
|
33
|
+
displayName: 'Review Plan',
|
|
32
34
|
description: 'Request user review of the current plan. Shows the full plan content for review with options to approve, request changes, or reject. Use after creating or updating a plan to get user approval before implementation.',
|
|
33
35
|
inputSchema: PlanReviewInputSchema,
|
|
34
36
|
/**
|
|
@@ -36,12 +38,13 @@ export function createPlanReviewTool(planService) {
|
|
|
36
38
|
* The ApprovalPrompt component detects plan_review and shows custom options.
|
|
37
39
|
*/
|
|
38
40
|
generatePreview: async (input, context) => {
|
|
41
|
+
const resolvedPlanService = await getPlanService(context);
|
|
39
42
|
const { summary } = input;
|
|
40
|
-
if (!context
|
|
43
|
+
if (!context.sessionId) {
|
|
41
44
|
throw PlanError.sessionIdRequired();
|
|
42
45
|
}
|
|
43
46
|
// Read the current plan
|
|
44
|
-
const plan = await
|
|
47
|
+
const plan = await resolvedPlanService.read(context.sessionId);
|
|
45
48
|
if (!plan) {
|
|
46
49
|
throw PlanError.planNotFound(context.sessionId);
|
|
47
50
|
}
|
|
@@ -51,7 +54,7 @@ export function createPlanReviewTool(planService) {
|
|
|
51
54
|
displayContent = `## Summary\n${summary}\n\n---\n\n${plan.content}`;
|
|
52
55
|
}
|
|
53
56
|
const lineCount = displayContent.split('\n').length;
|
|
54
|
-
const planPath =
|
|
57
|
+
const planPath = resolvedPlanService.getPlanPath(context.sessionId);
|
|
55
58
|
return {
|
|
56
59
|
type: 'file',
|
|
57
60
|
path: planPath,
|
|
@@ -61,24 +64,25 @@ export function createPlanReviewTool(planService) {
|
|
|
61
64
|
lineCount,
|
|
62
65
|
};
|
|
63
66
|
},
|
|
64
|
-
|
|
67
|
+
async execute(_input, context) {
|
|
68
|
+
const resolvedPlanService = await getPlanService(context);
|
|
65
69
|
// Tool execution means user approved the plan (selected Approve or Approve + Accept Edits)
|
|
66
70
|
// Request Changes and Reject are handled as denials in the approval flow
|
|
67
|
-
if (!context
|
|
71
|
+
if (!context.sessionId) {
|
|
68
72
|
throw PlanError.sessionIdRequired();
|
|
69
73
|
}
|
|
70
74
|
// Read plan to verify it still exists
|
|
71
|
-
const plan = await
|
|
75
|
+
const plan = await resolvedPlanService.read(context.sessionId);
|
|
72
76
|
if (!plan) {
|
|
73
77
|
throw PlanError.planNotFound(context.sessionId);
|
|
74
78
|
}
|
|
75
79
|
// Update plan status to approved
|
|
76
|
-
await
|
|
80
|
+
await resolvedPlanService.updateMeta(context.sessionId, { status: 'approved' });
|
|
77
81
|
return {
|
|
78
82
|
approved: true,
|
|
79
83
|
message: 'Plan approved. You may now proceed with implementation.',
|
|
80
84
|
planStatus: 'approved',
|
|
81
85
|
};
|
|
82
86
|
},
|
|
83
|
-
};
|
|
87
|
+
});
|
|
84
88
|
}
|
|
@@ -4,10 +4,19 @@
|
|
|
4
4
|
* Updates the implementation plan for the current session.
|
|
5
5
|
* Shows a diff preview for approval before saving.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
8
|
-
import type {
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import type { Tool } from '@dexto/core';
|
|
9
|
+
import type { PlanServiceGetter } from '../plan-service-getter.js';
|
|
10
|
+
declare const PlanUpdateInputSchema: z.ZodObject<{
|
|
11
|
+
content: z.ZodString;
|
|
12
|
+
}, "strict", z.ZodTypeAny, {
|
|
13
|
+
content: string;
|
|
14
|
+
}, {
|
|
15
|
+
content: string;
|
|
16
|
+
}>;
|
|
9
17
|
/**
|
|
10
18
|
* Creates the plan_update tool
|
|
11
19
|
*/
|
|
12
|
-
export declare function createPlanUpdateTool(
|
|
20
|
+
export declare function createPlanUpdateTool(getPlanService: PlanServiceGetter): Tool<typeof PlanUpdateInputSchema>;
|
|
21
|
+
export {};
|
|
13
22
|
//# sourceMappingURL=plan-update-tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan-update-tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-update-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"plan-update-tool.d.ts","sourceRoot":"","sources":["../../src/tools/plan-update-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,IAAI,EAAyC,MAAM,aAAa,CAAC;AAC/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAGnE,QAAA,MAAM,qBAAqB;;;;;;EAId,CAAC;AAyBd;;GAEG;AACH,wBAAgB,oBAAoB,CAChC,cAAc,EAAE,iBAAiB,GAClC,IAAI,CAAC,OAAO,qBAAqB,CAAC,CAiDpC"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { createPatch } from 'diff';
|
|
9
|
+
import { defineTool } from '@dexto/core';
|
|
9
10
|
import { PlanError } from '../errors.js';
|
|
10
11
|
const PlanUpdateInputSchema = z
|
|
11
12
|
.object({
|
|
@@ -32,35 +33,38 @@ function generateDiffPreview(filePath, originalContent, newContent) {
|
|
|
32
33
|
/**
|
|
33
34
|
* Creates the plan_update tool
|
|
34
35
|
*/
|
|
35
|
-
export function createPlanUpdateTool(
|
|
36
|
-
return {
|
|
36
|
+
export function createPlanUpdateTool(getPlanService) {
|
|
37
|
+
return defineTool({
|
|
37
38
|
id: 'plan_update',
|
|
39
|
+
displayName: 'Update Plan',
|
|
38
40
|
description: 'Update the existing implementation plan for this session. Shows a diff preview for approval before saving. The plan must already exist (use plan_create first).',
|
|
39
41
|
inputSchema: PlanUpdateInputSchema,
|
|
40
42
|
/**
|
|
41
43
|
* Generate diff preview for approval UI
|
|
42
44
|
*/
|
|
43
45
|
generatePreview: async (input, context) => {
|
|
46
|
+
const resolvedPlanService = await getPlanService(context);
|
|
44
47
|
const { content: newContent } = input;
|
|
45
|
-
if (!context
|
|
48
|
+
if (!context.sessionId) {
|
|
46
49
|
throw PlanError.sessionIdRequired();
|
|
47
50
|
}
|
|
48
51
|
// Read existing plan
|
|
49
|
-
const existing = await
|
|
52
|
+
const existing = await resolvedPlanService.read(context.sessionId);
|
|
50
53
|
if (!existing) {
|
|
51
54
|
throw PlanError.planNotFound(context.sessionId);
|
|
52
55
|
}
|
|
53
56
|
// Generate diff preview
|
|
54
|
-
const planPath =
|
|
57
|
+
const planPath = resolvedPlanService.getPlanPath(context.sessionId);
|
|
55
58
|
return generateDiffPreview(planPath, existing.content, newContent);
|
|
56
59
|
},
|
|
57
|
-
|
|
60
|
+
async execute(input, context) {
|
|
61
|
+
const resolvedPlanService = await getPlanService(context);
|
|
58
62
|
const { content } = input;
|
|
59
|
-
if (!context
|
|
63
|
+
if (!context.sessionId) {
|
|
60
64
|
throw PlanError.sessionIdRequired();
|
|
61
65
|
}
|
|
62
|
-
const result = await
|
|
63
|
-
const planPath =
|
|
66
|
+
const result = await resolvedPlanService.update(context.sessionId, content);
|
|
67
|
+
const planPath = resolvedPlanService.getPlanPath(context.sessionId);
|
|
64
68
|
return {
|
|
65
69
|
success: true,
|
|
66
70
|
path: planPath,
|
|
@@ -68,5 +72,5 @@ export function createPlanUpdateTool(planService) {
|
|
|
68
72
|
_display: generateDiffPreview(planPath, result.oldContent, result.newContent),
|
|
69
73
|
};
|
|
70
74
|
},
|
|
71
|
-
};
|
|
75
|
+
});
|
|
72
76
|
}
|