@vibescope/mcp-server 0.1.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/README.md +1 -1
- package/dist/api-client.d.ts +120 -2
- package/dist/api-client.js +51 -5
- package/dist/handlers/bodies-of-work.js +84 -50
- package/dist/handlers/cost.js +62 -54
- package/dist/handlers/decisions.js +29 -16
- package/dist/handlers/deployment.js +114 -107
- package/dist/handlers/discovery.d.ts +3 -0
- package/dist/handlers/discovery.js +55 -657
- package/dist/handlers/fallback.js +42 -28
- package/dist/handlers/file-checkouts.d.ts +18 -0
- package/dist/handlers/file-checkouts.js +101 -0
- package/dist/handlers/findings.d.ts +14 -1
- package/dist/handlers/findings.js +104 -28
- 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 +52 -15
- package/dist/handlers/sprints.js +78 -65
- package/dist/handlers/tasks.js +135 -74
- package/dist/handlers/tool-docs.d.ts +4 -3
- package/dist/handlers/tool-docs.js +252 -5
- package/dist/handlers/validation.js +30 -14
- package/dist/index.js +25 -7
- package/dist/tools.js +417 -4
- package/package.json +1 -1
- package/src/api-client.ts +161 -8
- package/src/handlers/__test-setup__.ts +12 -0
- package/src/handlers/bodies-of-work.ts +127 -111
- 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 +144 -190
- package/src/handlers/discovery.test.ts +4 -5
- package/src/handlers/discovery.ts +60 -746
- package/src/handlers/fallback.test.ts +78 -0
- package/src/handlers/fallback.ts +51 -38
- package/src/handlers/file-checkouts.test.ts +477 -0
- package/src/handlers/file-checkouts.ts +127 -0
- package/src/handlers/findings.test.ts +274 -2
- package/src/handlers/findings.ts +123 -57
- 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.test.ts +37 -2
- package/src/handlers/session.ts +64 -21
- package/src/handlers/sprints.ts +114 -134
- package/src/handlers/tasks.test.ts +61 -0
- package/src/handlers/tasks.ts +170 -139
- package/src/handlers/tool-docs.ts +1024 -0
- package/src/handlers/validation.test.ts +53 -1
- package/src/handlers/validation.ts +32 -21
- package/src/index.ts +25 -7
- package/src/tools.ts +417 -4
- 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
- package/src/knowledge.ts +0 -230
|
@@ -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
|
+
};
|
|
@@ -31,7 +31,7 @@ describe('heartbeat', () => {
|
|
|
31
31
|
session_id: 'session-123',
|
|
32
32
|
});
|
|
33
33
|
expect(result.result).toHaveProperty('timestamp');
|
|
34
|
-
expect(mockApiClient.heartbeat).toHaveBeenCalledWith('session-123');
|
|
34
|
+
expect(mockApiClient.heartbeat).toHaveBeenCalledWith('session-123', { current_worktree_path: undefined });
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it('should use provided session_id over current session', async () => {
|
|
@@ -48,7 +48,20 @@ describe('heartbeat', () => {
|
|
|
48
48
|
success: true,
|
|
49
49
|
session_id: 'other-session-456',
|
|
50
50
|
});
|
|
51
|
-
expect(mockApiClient.heartbeat).toHaveBeenCalledWith('other-session-456');
|
|
51
|
+
expect(mockApiClient.heartbeat).toHaveBeenCalledWith('other-session-456', { current_worktree_path: undefined });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should pass worktree_path to API', async () => {
|
|
55
|
+
const ctx = createMockContext();
|
|
56
|
+
mockApiClient.heartbeat.mockResolvedValue({
|
|
57
|
+
ok: true,
|
|
58
|
+
data: { timestamp: '2026-01-14T10:00:00Z' },
|
|
59
|
+
});
|
|
60
|
+
mockApiClient.syncSession.mockResolvedValue({ ok: true });
|
|
61
|
+
|
|
62
|
+
await heartbeat({ current_worktree_path: '../project-task-abc123' }, ctx);
|
|
63
|
+
|
|
64
|
+
expect(mockApiClient.heartbeat).toHaveBeenCalledWith('session-123', { current_worktree_path: '../project-task-abc123' });
|
|
52
65
|
});
|
|
53
66
|
|
|
54
67
|
it('should return error when no active session', async () => {
|
|
@@ -101,6 +114,10 @@ describe('getHelp', () => {
|
|
|
101
114
|
|
|
102
115
|
it('should return help content for valid topic', async () => {
|
|
103
116
|
const ctx = createMockContext();
|
|
117
|
+
mockApiClient.getHelpTopic.mockResolvedValue({
|
|
118
|
+
ok: true,
|
|
119
|
+
data: { slug: 'tasks', title: 'Task Workflow', content: '# Task Workflow\nTest content' },
|
|
120
|
+
});
|
|
104
121
|
|
|
105
122
|
const result = await getHelp({ topic: 'tasks' }, ctx);
|
|
106
123
|
|
|
@@ -110,6 +127,10 @@ describe('getHelp', () => {
|
|
|
110
127
|
|
|
111
128
|
it('should return getting_started help', async () => {
|
|
112
129
|
const ctx = createMockContext();
|
|
130
|
+
mockApiClient.getHelpTopic.mockResolvedValue({
|
|
131
|
+
ok: true,
|
|
132
|
+
data: { slug: 'getting_started', title: 'Getting Started', content: '# Getting Started\nTest content' },
|
|
133
|
+
});
|
|
113
134
|
|
|
114
135
|
const result = await getHelp({ topic: 'getting_started' }, ctx);
|
|
115
136
|
|
|
@@ -119,6 +140,11 @@ describe('getHelp', () => {
|
|
|
119
140
|
|
|
120
141
|
it('should return error for unknown topic', async () => {
|
|
121
142
|
const ctx = createMockContext();
|
|
143
|
+
mockApiClient.getHelpTopic.mockResolvedValue({ ok: true, data: null });
|
|
144
|
+
mockApiClient.getHelpTopics.mockResolvedValue({
|
|
145
|
+
ok: true,
|
|
146
|
+
data: [{ slug: 'tasks', title: 'Tasks' }, { slug: 'getting_started', title: 'Getting Started' }],
|
|
147
|
+
});
|
|
122
148
|
|
|
123
149
|
const result = await getHelp({ topic: 'unknown_topic' }, ctx);
|
|
124
150
|
|
|
@@ -130,6 +156,15 @@ describe('getHelp', () => {
|
|
|
130
156
|
|
|
131
157
|
it('should list available topics for unknown topic', async () => {
|
|
132
158
|
const ctx = createMockContext();
|
|
159
|
+
mockApiClient.getHelpTopic.mockResolvedValue({ ok: true, data: null });
|
|
160
|
+
mockApiClient.getHelpTopics.mockResolvedValue({
|
|
161
|
+
ok: true,
|
|
162
|
+
data: [
|
|
163
|
+
{ slug: 'tasks', title: 'Tasks' },
|
|
164
|
+
{ slug: 'getting_started', title: 'Getting Started' },
|
|
165
|
+
{ slug: 'validation', title: 'Validation' },
|
|
166
|
+
],
|
|
167
|
+
});
|
|
133
168
|
|
|
134
169
|
const result = await getHelp({ topic: 'nonexistent' }, ctx);
|
|
135
170
|
|