@vibescope/mcp-server 0.2.0 → 0.2.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/dist/api-client.d.ts +64 -1
- package/dist/api-client.js +34 -3
- package/dist/handlers/bodies-of-work.js +82 -49
- package/dist/handlers/cost.js +62 -54
- package/dist/handlers/decisions.js +29 -16
- package/dist/handlers/deployment.js +112 -106
- package/dist/handlers/discovery.js +35 -5
- package/dist/handlers/fallback.js +24 -19
- package/dist/handlers/file-checkouts.d.ts +18 -0
- package/dist/handlers/file-checkouts.js +101 -0
- package/dist/handlers/findings.d.ts +6 -0
- package/dist/handlers/findings.js +85 -30
- package/dist/handlers/git-issues.js +36 -32
- package/dist/handlers/ideas.js +44 -26
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.js +6 -0
- package/dist/handlers/milestones.js +34 -27
- package/dist/handlers/organizations.js +86 -78
- package/dist/handlers/progress.js +22 -11
- package/dist/handlers/project.js +62 -22
- package/dist/handlers/requests.js +15 -11
- package/dist/handlers/roles.d.ts +18 -0
- package/dist/handlers/roles.js +130 -0
- package/dist/handlers/session.js +30 -8
- package/dist/handlers/sprints.js +76 -64
- package/dist/handlers/tasks.js +113 -73
- package/dist/handlers/validation.js +18 -14
- package/dist/tools.js +387 -0
- package/package.json +1 -1
- package/src/api-client.ts +89 -6
- package/src/handlers/__test-setup__.ts +7 -0
- package/src/handlers/bodies-of-work.ts +101 -101
- package/src/handlers/cost.test.ts +34 -44
- package/src/handlers/cost.ts +77 -92
- package/src/handlers/decisions.test.ts +3 -2
- package/src/handlers/decisions.ts +32 -27
- package/src/handlers/deployment.ts +142 -190
- package/src/handlers/discovery.test.ts +4 -5
- package/src/handlers/discovery.ts +37 -6
- package/src/handlers/fallback.ts +31 -29
- package/src/handlers/file-checkouts.test.ts +477 -0
- package/src/handlers/file-checkouts.ts +127 -0
- package/src/handlers/findings.test.ts +145 -0
- package/src/handlers/findings.ts +101 -64
- package/src/handlers/git-issues.ts +40 -80
- package/src/handlers/ideas.ts +56 -54
- package/src/handlers/index.ts +6 -0
- package/src/handlers/milestones.test.ts +1 -1
- package/src/handlers/milestones.ts +47 -45
- package/src/handlers/organizations.ts +104 -129
- package/src/handlers/progress.ts +24 -22
- package/src/handlers/project.ts +89 -57
- package/src/handlers/requests.ts +18 -14
- package/src/handlers/roles.test.ts +303 -0
- package/src/handlers/roles.ts +208 -0
- package/src/handlers/session.ts +39 -17
- package/src/handlers/sprints.ts +96 -129
- package/src/handlers/tasks.ts +144 -138
- package/src/handlers/validation.test.ts +1 -1
- package/src/handlers/validation.ts +20 -22
- package/src/tools.ts +387 -0
- package/dist/config/tool-categories.d.ts +0 -31
- package/dist/config/tool-categories.js +0 -253
- package/dist/knowledge.d.ts +0 -6
- package/dist/knowledge.js +0 -218
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
getRoleSettings,
|
|
4
|
+
updateRoleSettings,
|
|
5
|
+
setSessionRole,
|
|
6
|
+
getAgentsByRole,
|
|
7
|
+
} from './roles.js';
|
|
8
|
+
import { createMockContext } from './__test-utils__.js';
|
|
9
|
+
import { mockApiClient } from './__test-setup__.js';
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// getRoleSettings Tests
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
describe('getRoleSettings', () => {
|
|
16
|
+
beforeEach(() => vi.clearAllMocks());
|
|
17
|
+
|
|
18
|
+
it('should return error for missing project_id', async () => {
|
|
19
|
+
const ctx = createMockContext();
|
|
20
|
+
|
|
21
|
+
const result = await getRoleSettings({}, ctx);
|
|
22
|
+
|
|
23
|
+
expect(result.result).toMatchObject({
|
|
24
|
+
error: 'project_id is required',
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should get role settings successfully', async () => {
|
|
29
|
+
const mockRoles = [
|
|
30
|
+
{ role: 'developer', enabled: true, display_name: 'Developer' },
|
|
31
|
+
{ role: 'validator', enabled: true, display_name: 'Validator' },
|
|
32
|
+
];
|
|
33
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
34
|
+
ok: true,
|
|
35
|
+
data: { roles: mockRoles },
|
|
36
|
+
});
|
|
37
|
+
const ctx = createMockContext();
|
|
38
|
+
|
|
39
|
+
const result = await getRoleSettings(
|
|
40
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
41
|
+
ctx
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(result.result).toMatchObject({ roles: mockRoles });
|
|
45
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
46
|
+
'get_role_settings',
|
|
47
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' }
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should return error from API', async () => {
|
|
52
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
53
|
+
ok: false,
|
|
54
|
+
error: 'Project not found',
|
|
55
|
+
});
|
|
56
|
+
const ctx = createMockContext();
|
|
57
|
+
|
|
58
|
+
const result = await getRoleSettings(
|
|
59
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
60
|
+
ctx
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(result.result).toMatchObject({
|
|
64
|
+
error: 'Project not found',
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// updateRoleSettings Tests
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
describe('updateRoleSettings', () => {
|
|
74
|
+
beforeEach(() => vi.clearAllMocks());
|
|
75
|
+
|
|
76
|
+
it('should return error for missing project_id', async () => {
|
|
77
|
+
const ctx = createMockContext();
|
|
78
|
+
|
|
79
|
+
const result = await updateRoleSettings({ role: 'developer' }, ctx);
|
|
80
|
+
|
|
81
|
+
expect(result.result).toMatchObject({
|
|
82
|
+
error: 'project_id is required',
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should return error for missing role', async () => {
|
|
87
|
+
const ctx = createMockContext();
|
|
88
|
+
|
|
89
|
+
const result = await updateRoleSettings(
|
|
90
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
91
|
+
ctx
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(result.result).toMatchObject({
|
|
95
|
+
error: 'role is required',
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should update role settings successfully', async () => {
|
|
100
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
101
|
+
ok: true,
|
|
102
|
+
data: { success: true, role: 'validator' },
|
|
103
|
+
});
|
|
104
|
+
const ctx = createMockContext();
|
|
105
|
+
|
|
106
|
+
const result = await updateRoleSettings(
|
|
107
|
+
{
|
|
108
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
109
|
+
role: 'validator',
|
|
110
|
+
enabled: true,
|
|
111
|
+
auto_assign_validation: true,
|
|
112
|
+
},
|
|
113
|
+
ctx
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect(result.result).toMatchObject({
|
|
117
|
+
success: true,
|
|
118
|
+
role: 'validator',
|
|
119
|
+
});
|
|
120
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
121
|
+
'update_role_settings',
|
|
122
|
+
expect.objectContaining({
|
|
123
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
124
|
+
role: 'validator',
|
|
125
|
+
enabled: true,
|
|
126
|
+
auto_assign_validation: true,
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should return error from API', async () => {
|
|
132
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
133
|
+
ok: false,
|
|
134
|
+
error: 'Access denied',
|
|
135
|
+
});
|
|
136
|
+
const ctx = createMockContext();
|
|
137
|
+
|
|
138
|
+
const result = await updateRoleSettings(
|
|
139
|
+
{
|
|
140
|
+
project_id: '123e4567-e89b-12d3-a456-426614174000',
|
|
141
|
+
role: 'validator',
|
|
142
|
+
},
|
|
143
|
+
ctx
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(result.result).toMatchObject({
|
|
147
|
+
error: 'Access denied',
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// setSessionRole Tests
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
describe('setSessionRole', () => {
|
|
157
|
+
beforeEach(() => vi.clearAllMocks());
|
|
158
|
+
|
|
159
|
+
it('should return error for missing role', async () => {
|
|
160
|
+
const ctx = createMockContext();
|
|
161
|
+
|
|
162
|
+
const result = await setSessionRole({}, ctx);
|
|
163
|
+
|
|
164
|
+
expect(result.result).toMatchObject({
|
|
165
|
+
error: 'role is required',
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should return error for invalid role', async () => {
|
|
170
|
+
const ctx = createMockContext();
|
|
171
|
+
|
|
172
|
+
const result = await setSessionRole({ role: 'invalid_role' }, ctx);
|
|
173
|
+
|
|
174
|
+
expect(result.result).toMatchObject({
|
|
175
|
+
error: expect.stringContaining('Invalid role: invalid_role'),
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should set local role when no active session', async () => {
|
|
180
|
+
const ctx = createMockContext({ sessionId: null });
|
|
181
|
+
|
|
182
|
+
const result = await setSessionRole({ role: 'validator' }, ctx);
|
|
183
|
+
|
|
184
|
+
expect(result.result).toMatchObject({
|
|
185
|
+
success: true,
|
|
186
|
+
role: 'validator',
|
|
187
|
+
message: expect.stringContaining('Local role set to validator'),
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should update session role on server when session active', async () => {
|
|
192
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
193
|
+
ok: true,
|
|
194
|
+
data: { success: true, session_id: 'session-123', role: 'deployer' },
|
|
195
|
+
});
|
|
196
|
+
const ctx = createMockContext({ sessionId: 'session-123' });
|
|
197
|
+
|
|
198
|
+
const result = await setSessionRole({ role: 'deployer' }, ctx);
|
|
199
|
+
|
|
200
|
+
expect(result.result).toMatchObject({
|
|
201
|
+
success: true,
|
|
202
|
+
session_id: 'session-123',
|
|
203
|
+
role: 'deployer',
|
|
204
|
+
});
|
|
205
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
206
|
+
'set_session_role',
|
|
207
|
+
expect.objectContaining({
|
|
208
|
+
session_id: 'session-123',
|
|
209
|
+
role: 'deployer',
|
|
210
|
+
})
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should update local state when setting role', async () => {
|
|
215
|
+
const updateSession = vi.fn();
|
|
216
|
+
const ctx = createMockContext({ sessionId: null });
|
|
217
|
+
ctx.updateSession = updateSession;
|
|
218
|
+
|
|
219
|
+
await setSessionRole({ role: 'reviewer' }, ctx);
|
|
220
|
+
|
|
221
|
+
expect(updateSession).toHaveBeenCalledWith({ currentRole: 'reviewer' });
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should return error from API when updating session', async () => {
|
|
225
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
226
|
+
ok: false,
|
|
227
|
+
error: 'Session not found',
|
|
228
|
+
});
|
|
229
|
+
const ctx = createMockContext({ sessionId: 'session-123' });
|
|
230
|
+
|
|
231
|
+
const result = await setSessionRole({ role: 'maintainer' }, ctx);
|
|
232
|
+
|
|
233
|
+
expect(result.result).toMatchObject({
|
|
234
|
+
error: 'Session not found',
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// getAgentsByRole Tests
|
|
241
|
+
// ============================================================================
|
|
242
|
+
|
|
243
|
+
describe('getAgentsByRole', () => {
|
|
244
|
+
beforeEach(() => vi.clearAllMocks());
|
|
245
|
+
|
|
246
|
+
it('should return error for missing project_id', async () => {
|
|
247
|
+
const ctx = createMockContext();
|
|
248
|
+
|
|
249
|
+
const result = await getAgentsByRole({}, ctx);
|
|
250
|
+
|
|
251
|
+
expect(result.result).toMatchObject({
|
|
252
|
+
error: 'project_id is required',
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should get agents by role successfully', async () => {
|
|
257
|
+
const mockAgentsByRole = {
|
|
258
|
+
developer: [
|
|
259
|
+
{ session_id: 'session-1', agent_name: 'Edge', status: 'active' },
|
|
260
|
+
],
|
|
261
|
+
validator: [],
|
|
262
|
+
deployer: [],
|
|
263
|
+
reviewer: [],
|
|
264
|
+
maintainer: [],
|
|
265
|
+
};
|
|
266
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
267
|
+
ok: true,
|
|
268
|
+
data: { agents_by_role: mockAgentsByRole, total_active: 1 },
|
|
269
|
+
});
|
|
270
|
+
const ctx = createMockContext();
|
|
271
|
+
|
|
272
|
+
const result = await getAgentsByRole(
|
|
273
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
274
|
+
ctx
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
expect(result.result).toMatchObject({
|
|
278
|
+
agents_by_role: mockAgentsByRole,
|
|
279
|
+
total_active: 1,
|
|
280
|
+
});
|
|
281
|
+
expect(mockApiClient.proxy).toHaveBeenCalledWith(
|
|
282
|
+
'get_agents_by_role',
|
|
283
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' }
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should return error from API', async () => {
|
|
288
|
+
mockApiClient.proxy.mockResolvedValue({
|
|
289
|
+
ok: false,
|
|
290
|
+
error: 'Project not found',
|
|
291
|
+
});
|
|
292
|
+
const ctx = createMockContext();
|
|
293
|
+
|
|
294
|
+
const result = await getAgentsByRole(
|
|
295
|
+
{ project_id: '123e4567-e89b-12d3-a456-426614174000' },
|
|
296
|
+
ctx
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(result.result).toMatchObject({
|
|
300
|
+
error: 'Project not found',
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role Management Handlers
|
|
3
|
+
*
|
|
4
|
+
* Handles agent role configuration and assignment:
|
|
5
|
+
* - get_role_settings: Get role configuration for a project
|
|
6
|
+
* - update_role_settings: Update role settings for a project
|
|
7
|
+
* - set_session_role: Set the role for the current session
|
|
8
|
+
* - get_agents_by_role: Get active agents grouped by their assigned roles
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Handler, HandlerRegistry, AgentRole } from './types.js';
|
|
12
|
+
import { getApiClient } from '../api-client.js';
|
|
13
|
+
|
|
14
|
+
export const getRoleSettings: Handler = async (args, _ctx) => {
|
|
15
|
+
const { project_id } = args as { project_id: string };
|
|
16
|
+
|
|
17
|
+
if (!project_id) {
|
|
18
|
+
return {
|
|
19
|
+
result: { error: 'project_id is required' },
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const apiClient = getApiClient();
|
|
24
|
+
const response = await apiClient.proxy<{
|
|
25
|
+
roles: Array<{
|
|
26
|
+
role: AgentRole;
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
display_name: string | null;
|
|
29
|
+
description: string | null;
|
|
30
|
+
priority_filter: number[] | null;
|
|
31
|
+
fallback_activities: string[] | null;
|
|
32
|
+
auto_assign_validation: boolean;
|
|
33
|
+
auto_assign_deployment: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
}>('get_role_settings', { project_id });
|
|
36
|
+
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
return {
|
|
39
|
+
result: { error: response.error || 'Failed to get role settings' },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { result: response.data };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const updateRoleSettings: Handler = async (args, _ctx) => {
|
|
47
|
+
const {
|
|
48
|
+
project_id,
|
|
49
|
+
role,
|
|
50
|
+
enabled,
|
|
51
|
+
display_name,
|
|
52
|
+
description,
|
|
53
|
+
priority_filter,
|
|
54
|
+
fallback_activities,
|
|
55
|
+
auto_assign_validation,
|
|
56
|
+
auto_assign_deployment,
|
|
57
|
+
} = args as {
|
|
58
|
+
project_id: string;
|
|
59
|
+
role: AgentRole;
|
|
60
|
+
enabled?: boolean;
|
|
61
|
+
display_name?: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
priority_filter?: number[];
|
|
64
|
+
fallback_activities?: string[];
|
|
65
|
+
auto_assign_validation?: boolean;
|
|
66
|
+
auto_assign_deployment?: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (!project_id) {
|
|
70
|
+
return {
|
|
71
|
+
result: { error: 'project_id is required' },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!role) {
|
|
76
|
+
return {
|
|
77
|
+
result: { error: 'role is required' },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const apiClient = getApiClient();
|
|
82
|
+
const response = await apiClient.proxy<{
|
|
83
|
+
success: boolean;
|
|
84
|
+
role: AgentRole;
|
|
85
|
+
}>('update_role_settings', {
|
|
86
|
+
project_id,
|
|
87
|
+
role,
|
|
88
|
+
enabled,
|
|
89
|
+
display_name,
|
|
90
|
+
description,
|
|
91
|
+
priority_filter,
|
|
92
|
+
fallback_activities,
|
|
93
|
+
auto_assign_validation,
|
|
94
|
+
auto_assign_deployment,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
return {
|
|
99
|
+
result: { error: response.error || 'Failed to update role settings' },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { result: response.data };
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const setSessionRole: Handler = async (args, ctx) => {
|
|
107
|
+
const { role, role_config } = args as {
|
|
108
|
+
role: AgentRole;
|
|
109
|
+
role_config?: Record<string, unknown>;
|
|
110
|
+
};
|
|
111
|
+
const { session, updateSession } = ctx;
|
|
112
|
+
|
|
113
|
+
if (!role) {
|
|
114
|
+
return {
|
|
115
|
+
result: { error: 'role is required' },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const validRoles: AgentRole[] = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'];
|
|
120
|
+
if (!validRoles.includes(role)) {
|
|
121
|
+
return {
|
|
122
|
+
result: {
|
|
123
|
+
error: `Invalid role: ${role}. Must be one of: ${validRoles.join(', ')}`,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Update local session state
|
|
129
|
+
updateSession({ currentRole: role });
|
|
130
|
+
|
|
131
|
+
// If there's an active session, update it on the server too
|
|
132
|
+
if (session.currentSessionId) {
|
|
133
|
+
const apiClient = getApiClient();
|
|
134
|
+
const response = await apiClient.proxy<{
|
|
135
|
+
success: boolean;
|
|
136
|
+
session_id: string;
|
|
137
|
+
role: AgentRole;
|
|
138
|
+
}>('set_session_role', {
|
|
139
|
+
session_id: session.currentSessionId,
|
|
140
|
+
role,
|
|
141
|
+
role_config,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
return {
|
|
146
|
+
result: { error: response.error || 'Failed to update session role' },
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
result: {
|
|
152
|
+
success: true,
|
|
153
|
+
session_id: session.currentSessionId,
|
|
154
|
+
role,
|
|
155
|
+
message: `Session role updated to ${role}. Task filtering and fallback suggestions will now be optimized for this role.`,
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
result: {
|
|
162
|
+
success: true,
|
|
163
|
+
role,
|
|
164
|
+
message: `Local role set to ${role}. Start a session to persist this role.`,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export const getAgentsByRole: Handler = async (args, _ctx) => {
|
|
170
|
+
const { project_id } = args as { project_id: string };
|
|
171
|
+
|
|
172
|
+
if (!project_id) {
|
|
173
|
+
return {
|
|
174
|
+
result: { error: 'project_id is required' },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const apiClient = getApiClient();
|
|
179
|
+
const response = await apiClient.proxy<{
|
|
180
|
+
agents_by_role: Record<AgentRole, Array<{
|
|
181
|
+
session_id: string;
|
|
182
|
+
agent_name: string;
|
|
183
|
+
status: string;
|
|
184
|
+
current_task_id: string | null;
|
|
185
|
+
current_task_title: string | null;
|
|
186
|
+
last_synced_at: string;
|
|
187
|
+
}>>;
|
|
188
|
+
total_active: number;
|
|
189
|
+
}>('get_agents_by_role', { project_id });
|
|
190
|
+
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
return {
|
|
193
|
+
result: { error: response.error || 'Failed to get agents by role' },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { result: response.data };
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Role handlers registry
|
|
202
|
+
*/
|
|
203
|
+
export const roleHandlers: HandlerRegistry = {
|
|
204
|
+
get_role_settings: getRoleSettings,
|
|
205
|
+
update_role_settings: updateRoleSettings,
|
|
206
|
+
set_session_role: setSessionRole,
|
|
207
|
+
get_agents_by_role: getAgentsByRole,
|
|
208
|
+
};
|
package/src/handlers/session.ts
CHANGED
|
@@ -10,22 +10,47 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import type { Handler, HandlerRegistry, TokenUsage } from './types.js';
|
|
13
|
+
import { parseArgs, createEnumValidator } from '../validators.js';
|
|
13
14
|
import { getApiClient } from '../api-client.js';
|
|
14
15
|
|
|
16
|
+
const VALID_MODES = ['lite', 'full'] as const;
|
|
17
|
+
const VALID_MODELS = ['opus', 'sonnet', 'haiku'] as const;
|
|
18
|
+
const VALID_ROLES = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'] as const;
|
|
19
|
+
|
|
20
|
+
type SessionMode = typeof VALID_MODES[number];
|
|
21
|
+
type SessionModel = typeof VALID_MODELS[number];
|
|
22
|
+
type SessionRole = typeof VALID_ROLES[number];
|
|
23
|
+
|
|
24
|
+
// Argument schemas for type-safe parsing
|
|
25
|
+
const startWorkSessionSchema = {
|
|
26
|
+
project_id: { type: 'string' as const },
|
|
27
|
+
git_url: { type: 'string' as const },
|
|
28
|
+
mode: { type: 'string' as const, default: 'lite', validate: createEnumValidator(VALID_MODES) },
|
|
29
|
+
model: { type: 'string' as const, validate: createEnumValidator(VALID_MODELS) },
|
|
30
|
+
role: { type: 'string' as const, default: 'developer', validate: createEnumValidator(VALID_ROLES) },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const heartbeatSchema = {
|
|
34
|
+
session_id: { type: 'string' as const },
|
|
35
|
+
current_worktree_path: { type: 'string' as const },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const endWorkSessionSchema = {
|
|
39
|
+
session_id: { type: 'string' as const },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const getHelpSchema = {
|
|
43
|
+
topic: { type: 'string' as const, required: true as const },
|
|
44
|
+
};
|
|
45
|
+
|
|
15
46
|
export const startWorkSession: Handler = async (args, ctx) => {
|
|
16
|
-
const { project_id, git_url, mode
|
|
17
|
-
project_id?: string;
|
|
18
|
-
git_url?: string;
|
|
19
|
-
mode?: 'lite' | 'full';
|
|
20
|
-
model?: 'opus' | 'sonnet' | 'haiku';
|
|
21
|
-
role?: 'developer' | 'validator' | 'deployer' | 'reviewer' | 'maintainer';
|
|
22
|
-
};
|
|
47
|
+
const { project_id, git_url, mode, model, role } = parseArgs(args, startWorkSessionSchema);
|
|
23
48
|
|
|
24
49
|
const { session, updateSession } = ctx;
|
|
25
50
|
|
|
26
51
|
// Reset token tracking for new session with model info
|
|
27
52
|
const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
|
|
28
|
-
const validModel = normalizedModel &&
|
|
53
|
+
const validModel = normalizedModel && VALID_MODELS.includes(normalizedModel as SessionModel)
|
|
29
54
|
? normalizedModel
|
|
30
55
|
: null;
|
|
31
56
|
|
|
@@ -52,9 +77,9 @@ export const startWorkSession: Handler = async (args, ctx) => {
|
|
|
52
77
|
const response = await apiClient.startSession({
|
|
53
78
|
project_id,
|
|
54
79
|
git_url,
|
|
55
|
-
mode,
|
|
56
|
-
model,
|
|
57
|
-
role
|
|
80
|
+
mode: mode as SessionMode,
|
|
81
|
+
model: model as SessionModel | undefined,
|
|
82
|
+
role: role as SessionRole
|
|
58
83
|
});
|
|
59
84
|
|
|
60
85
|
if (!response.ok) {
|
|
@@ -139,10 +164,7 @@ export const startWorkSession: Handler = async (args, ctx) => {
|
|
|
139
164
|
};
|
|
140
165
|
|
|
141
166
|
export const heartbeat: Handler = async (args, ctx) => {
|
|
142
|
-
const { session_id, current_worktree_path } = args
|
|
143
|
-
session_id?: string;
|
|
144
|
-
current_worktree_path?: string | null;
|
|
145
|
-
};
|
|
167
|
+
const { session_id, current_worktree_path } = parseArgs(args, heartbeatSchema);
|
|
146
168
|
const { session } = ctx;
|
|
147
169
|
const targetSession = session_id || session.currentSessionId;
|
|
148
170
|
|
|
@@ -186,7 +208,7 @@ export const heartbeat: Handler = async (args, ctx) => {
|
|
|
186
208
|
};
|
|
187
209
|
|
|
188
210
|
export const endWorkSession: Handler = async (args, ctx) => {
|
|
189
|
-
const { session_id } = args
|
|
211
|
+
const { session_id } = parseArgs(args, endWorkSessionSchema);
|
|
190
212
|
const { session, updateSession } = ctx;
|
|
191
213
|
const targetSession = session_id || session.currentSessionId;
|
|
192
214
|
|
|
@@ -251,7 +273,7 @@ export const endWorkSession: Handler = async (args, ctx) => {
|
|
|
251
273
|
};
|
|
252
274
|
|
|
253
275
|
export const getHelp: Handler = async (args, _ctx) => {
|
|
254
|
-
const { topic } = args
|
|
276
|
+
const { topic } = parseArgs(args, getHelpSchema);
|
|
255
277
|
|
|
256
278
|
const apiClient = getApiClient();
|
|
257
279
|
const response = await apiClient.getHelpTopic(topic);
|