@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.
Files changed (43) hide show
  1. package/dist/index.d.ts +1 -25
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -29
  4. package/dist/plan-service-getter.d.ts +4 -0
  5. package/dist/plan-service-getter.d.ts.map +1 -0
  6. package/dist/plan-service-getter.js +1 -0
  7. package/dist/plan-service.d.ts +2 -2
  8. package/dist/plan-service.d.ts.map +1 -1
  9. package/dist/plan-service.js +7 -7
  10. package/dist/plan-service.test.js +6 -0
  11. package/dist/tool-factory-config.d.ts +32 -0
  12. package/dist/tool-factory-config.d.ts.map +1 -0
  13. package/dist/tool-factory-config.js +30 -0
  14. package/dist/tool-factory.d.ts +4 -0
  15. package/dist/tool-factory.d.ts.map +1 -0
  16. package/dist/tool-factory.js +36 -0
  17. package/dist/tool-factory.test.d.ts +7 -0
  18. package/dist/tool-factory.test.d.ts.map +1 -0
  19. package/dist/tool-factory.test.js +100 -0
  20. package/dist/tools/plan-create-tool.d.ts +15 -3
  21. package/dist/tools/plan-create-tool.d.ts.map +1 -1
  22. package/dist/tools/plan-create-tool.js +29 -18
  23. package/dist/tools/plan-create-tool.test.js +47 -22
  24. package/dist/tools/plan-read-tool.d.ts +6 -3
  25. package/dist/tools/plan-read-tool.d.ts.map +1 -1
  26. package/dist/tools/plan-read-tool.js +10 -7
  27. package/dist/tools/plan-read-tool.test.js +31 -19
  28. package/dist/tools/plan-review-tool.d.ts +14 -5
  29. package/dist/tools/plan-review-tool.d.ts.map +1 -1
  30. package/dist/tools/plan-review-tool.js +16 -12
  31. package/dist/tools/plan-update-tool.d.ts +12 -3
  32. package/dist/tools/plan-update-tool.d.ts.map +1 -1
  33. package/dist/tools/plan-update-tool.js +14 -10
  34. package/dist/tools/plan-update-tool.test.js +40 -28
  35. package/package.json +4 -5
  36. package/.dexto-plugin/plugin.json +0 -7
  37. package/dist/tool-provider.d.ts +0 -44
  38. package/dist/tool-provider.d.ts.map +0 -1
  39. package/dist/tool-provider.js +0 -81
  40. package/dist/tool-provider.test.d.ts +0 -7
  41. package/dist/tool-provider.test.d.ts.map +0 -1
  42. package/dist/tool-provider.test.js +0 -185
  43. 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
- debug: vi.fn(),
17
- info: vi.fn(),
18
- warn: vi.fn(),
19
- error: vi.fn(),
20
- createChild: vi.fn().mockReturnThis(),
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 mockLogger;
35
+ let logger;
24
36
  let tempDir;
25
37
  let planService;
26
38
  beforeEach(async () => {
27
- mockLogger = createMockLogger();
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 }, mockLogger);
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 type { InternalTool } from '@dexto/core';
8
- import type { PlanService } from '../plan-service.js';
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(planService: PlanService): InternalTool;
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;AAGH,OAAO,KAAK,EAAE,YAAY,EAAwB,MAAM,aAAa,CAAC;AACtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKtD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,WAAW,GAAG,YAAY,CAgCzE"}
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(planService) {
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
- execute: async (_input, context) => {
19
- if (!context?.sessionId) {
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 planService.read(context.sessionId);
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: `.dexto/plans/${context.sessionId}/plan.md`,
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
- debug: vi.fn(),
17
- info: vi.fn(),
18
- warn: vi.fn(),
19
- error: vi.fn(),
20
- createChild: vi.fn().mockReturnThis(),
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 mockLogger;
35
+ let logger;
24
36
  let tempDir;
25
37
  let planService;
26
38
  beforeEach(async () => {
27
- mockLogger = createMockLogger();
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 }, mockLogger);
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(`.dexto/plans/${sessionId}/plan.md`);
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 confirmation pattern (not elicitation) so the user
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 type { InternalTool } from '@dexto/core';
15
- import type { PlanService } from '../plan-service.js';
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 planService - Service for plan operations
27
+ * @param getPlanService - Getter for the plan service
20
28
  */
21
- export declare function createPlanReviewTool(planService: PlanService): InternalTool;
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;AAGH,OAAO,KAAK,EAAE,YAAY,EAAyC,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AActD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,WAAW,GAAG,YAAY,CAoE3E"}
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 confirmation pattern (not elicitation) so the user
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 planService - Service for plan operations
28
+ * @param getPlanService - Getter for the plan service
28
29
  */
29
- export function createPlanReviewTool(planService) {
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?.sessionId) {
43
+ if (!context.sessionId) {
41
44
  throw PlanError.sessionIdRequired();
42
45
  }
43
46
  // Read the current plan
44
- const plan = await planService.read(context.sessionId);
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 = planService.getPlanPath(context.sessionId);
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
- execute: async (_input, context) => {
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?.sessionId) {
71
+ if (!context.sessionId) {
68
72
  throw PlanError.sessionIdRequired();
69
73
  }
70
74
  // Read plan to verify it still exists
71
- const plan = await planService.read(context.sessionId);
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 planService.updateMeta(context.sessionId, { status: 'approved' });
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 type { InternalTool } from '@dexto/core';
8
- import type { PlanService } from '../plan-service.js';
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(planService: PlanService): InternalTool;
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;AAIH,OAAO,KAAK,EAAE,YAAY,EAAyC,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAkCtD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,WAAW,GAAG,YAAY,CAiD3E"}
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(planService) {
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?.sessionId) {
48
+ if (!context.sessionId) {
46
49
  throw PlanError.sessionIdRequired();
47
50
  }
48
51
  // Read existing plan
49
- const existing = await planService.read(context.sessionId);
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 = planService.getPlanPath(context.sessionId);
57
+ const planPath = resolvedPlanService.getPlanPath(context.sessionId);
55
58
  return generateDiffPreview(planPath, existing.content, newContent);
56
59
  },
57
- execute: async (input, context) => {
60
+ async execute(input, context) {
61
+ const resolvedPlanService = await getPlanService(context);
58
62
  const { content } = input;
59
- if (!context?.sessionId) {
63
+ if (!context.sessionId) {
60
64
  throw PlanError.sessionIdRequired();
61
65
  }
62
- const result = await planService.update(context.sessionId, content);
63
- const planPath = planService.getPlanPath(context.sessionId);
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
  }