@ezmodo/mcp-server 0.19.1 → 0.20.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/config/endpoint-map.js +6 -0
- package/handlers/epics.js +55 -42
- package/handlers/tasks.js +25 -0
- package/lib/create-server.js +2 -1
- package/lib/tool-annotations.js +104 -0
- package/lib/version.js +1 -1
- package/package.json +1 -1
- package/tools/epics.js +45 -9
- package/tools/tasks.js +39 -2
package/config/endpoint-map.js
CHANGED
|
@@ -40,12 +40,18 @@ export const ENDPOINT_MAP = {
|
|
|
40
40
|
'mcpProposePlanChange': { route: 'mcp/v1/epics/proposals', method: 'POST' },
|
|
41
41
|
'mcpReviewPlanProposal': { route: 'mcp/v1/epics/proposals/review', method: 'POST' },
|
|
42
42
|
'mcpAddEpicComment': { route: 'mcp/v1/epics/comments', method: 'POST' },
|
|
43
|
+
// Editors (E-259 #2802): who the owner lets change the plan.
|
|
44
|
+
'mcpManageEpicEditors': { route: 'mcp/v1/epics/editors', method: 'POST' },
|
|
43
45
|
// E-237 #2382: the epic is the fifth consumer of the grounding engine.
|
|
44
46
|
'mcpGenerateEpicHowItWorks': { route: 'mcp/v1/epics/generate-how-it-works', method: 'POST' },
|
|
45
47
|
'mcpApplyEpicHowItWorks': { route: 'mcp/v1/epics/apply-how-it-works', method: 'POST' },
|
|
46
48
|
|
|
47
49
|
// Tasks
|
|
48
50
|
'mcpCreateTask': { route: 'mcp/v1/tasks', method: 'POST' },
|
|
51
|
+
// Claims (E-259 #2747): manage_task action "claim" / "release".
|
|
52
|
+
'mcpClaimTask': { route: 'mcp/v1/tasks/claim', method: 'POST' },
|
|
53
|
+
// Suggested edits on someone else's claimed task (E-259 #2809).
|
|
54
|
+
'mcpTaskSuggestedEdits': { route: 'mcp/v1/tasks/suggested-edits', method: 'POST' },
|
|
49
55
|
'mcpBulkCreateTasks': { route: 'mcp/v1/tasks/bulk', method: 'POST' },
|
|
50
56
|
'mcpUpdateTask': { route: 'mcp/v1/tasks', method: 'PUT' },
|
|
51
57
|
'mcpCompleteTask': { route: 'mcp/v1/tasks/complete', method: 'POST' },
|
package/handlers/epics.js
CHANGED
|
@@ -79,12 +79,23 @@ export async function manageEpic(args) {
|
|
|
79
79
|
case 'update': return updateEpic(params);
|
|
80
80
|
case 'generate_how_it_works': return generateEpicHowItWorks(params);
|
|
81
81
|
case 'apply_how_it_works': return applyEpicHowItWorks(params);
|
|
82
|
+
case 'add_editor': return manageEpicEditor('add', params);
|
|
83
|
+
case 'remove_editor': return manageEpicEditor('remove', params);
|
|
82
84
|
default: throw new Error(
|
|
83
|
-
`Unknown action: ${action}. Expected create, update, generate_how_it_works,
|
|
85
|
+
`Unknown action: ${action}. Expected create, update, generate_how_it_works, apply_how_it_works, ` +
|
|
86
|
+
'add_editor or remove_editor.',
|
|
84
87
|
);
|
|
85
88
|
}
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
// Choose who else may change an epic's plan (E-259 #2802). The server decides
|
|
92
|
+
// who may do this; the tool only checks it was asked something answerable.
|
|
93
|
+
async function manageEpicEditor(action, { epicId, editorUserId }) {
|
|
94
|
+
if (!epicId) throw new Error(`epicId is required to ${action} an editor`);
|
|
95
|
+
if (!editorUserId) throw new Error(`editorUserId is required to ${action} an editor`);
|
|
96
|
+
return callZephlyAPI('mcpManageEpicEditors', { epicId, userId: editorUserId, action });
|
|
97
|
+
}
|
|
98
|
+
|
|
88
99
|
// (Re)generate the epic's grounded, source-attributed "how it works" living
|
|
89
100
|
// description (E-237 #2382). An epic's reality is its tasks — their status,
|
|
90
101
|
// captured decisions and linked commits — and its intent is the epic description
|
|
@@ -231,6 +242,8 @@ export async function getEpicActivity(args) {
|
|
|
231
242
|
const params = { epicId: args.epicId };
|
|
232
243
|
if (args.since) params.since = args.since;
|
|
233
244
|
if (args.markSeen === false) params.markSeen = 'false';
|
|
245
|
+
// Live mode (#2748): the API waits for news, up to its own cap.
|
|
246
|
+
if (args.waitSeconds > 0) params.waitSeconds = Math.min(Math.floor(args.waitSeconds), 25);
|
|
234
247
|
return callZephlyAPI('mcpGetEpicActivity', params);
|
|
235
248
|
}
|
|
236
249
|
|
|
@@ -258,53 +271,53 @@ export async function managePlanProposal(args = {}) {
|
|
|
258
271
|
const { action } = args;
|
|
259
272
|
|
|
260
273
|
switch (action) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
return callZephlyAPI('mcpProposePlanChange', {
|
|
267
|
-
epicId: args.epicId,
|
|
268
|
-
plan: args.plan,
|
|
269
|
-
ops: args.ops,
|
|
270
|
-
title: args.title,
|
|
271
|
-
rationale: args.rationale,
|
|
272
|
-
});
|
|
274
|
+
case 'propose': {
|
|
275
|
+
if (!args.epicId) throw new Error('epicId is required to suggest a change');
|
|
276
|
+
if (!args.plan && !args.ops) {
|
|
277
|
+
throw new Error('Send the plan you want (or the individual changes) to suggest a change');
|
|
273
278
|
}
|
|
279
|
+
return callZephlyAPI('mcpProposePlanChange', {
|
|
280
|
+
epicId: args.epicId,
|
|
281
|
+
plan: args.plan,
|
|
282
|
+
ops: args.ops,
|
|
283
|
+
title: args.title,
|
|
284
|
+
rationale: args.rationale,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
274
287
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
288
|
+
case 'list': {
|
|
289
|
+
if (!args.epicId) throw new Error('epicId is required to list proposals');
|
|
290
|
+
const params = { epicId: args.epicId };
|
|
291
|
+
if (args.status) params.status = args.status;
|
|
292
|
+
return callZephlyAPI('mcpListPlanProposals', params);
|
|
293
|
+
}
|
|
281
294
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
case 'get': {
|
|
296
|
+
if (!args.proposalId) throw new Error('proposalId is required');
|
|
297
|
+
return callZephlyAPI('mcpListPlanProposals', { proposalId: args.proposalId });
|
|
298
|
+
}
|
|
286
299
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
return callZephlyAPI('mcpReviewPlanProposal', {
|
|
293
|
-
proposalId: args.proposalId,
|
|
294
|
-
accept: args.accept || [],
|
|
295
|
-
reject: args.reject || [],
|
|
296
|
-
note: args.note,
|
|
297
|
-
});
|
|
300
|
+
case 'review': {
|
|
301
|
+
if (!args.proposalId) throw new Error('proposalId is required to answer a proposal');
|
|
302
|
+
if (!args.accept?.length && !args.reject?.length) {
|
|
303
|
+
throw new Error('Say which changes you are taking (accept) and which you are not (reject)');
|
|
298
304
|
}
|
|
305
|
+
return callZephlyAPI('mcpReviewPlanProposal', {
|
|
306
|
+
proposalId: args.proposalId,
|
|
307
|
+
accept: args.accept || [],
|
|
308
|
+
reject: args.reject || [],
|
|
309
|
+
note: args.note,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
299
312
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
313
|
+
case 'withdraw': {
|
|
314
|
+
if (!args.proposalId) throw new Error('proposalId is required to take back a proposal');
|
|
315
|
+
return callZephlyAPI('mcpReviewPlanProposal', { proposalId: args.proposalId, withdraw: true });
|
|
316
|
+
}
|
|
304
317
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
318
|
+
default:
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Unknown action "${action}". Use propose, list, get, review or withdraw.`
|
|
321
|
+
);
|
|
309
322
|
}
|
|
310
323
|
}
|
package/handlers/tasks.js
CHANGED
|
@@ -54,10 +54,35 @@ export async function manageTask(args) {
|
|
|
54
54
|
case 'get_commits': return getTaskCommits(params);
|
|
55
55
|
case 'generate_how_it_works': return generateTaskHowItWorks(params);
|
|
56
56
|
case 'apply_how_it_works': return applyTaskHowItWorks(params);
|
|
57
|
+
case 'claim': return claimTask(params, false);
|
|
58
|
+
case 'release': return claimTask(params, true);
|
|
59
|
+
case 'list_suggested_edits': return suggestedEdits(params, false);
|
|
60
|
+
case 'answer_suggested_edit': return suggestedEdits(params, true);
|
|
57
61
|
default: throw new Error(`Unknown action: ${action}`);
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Claim a task, or give it back (E-259 #2747). The server decides who may and
|
|
67
|
+
* words the answer; a task someone else holds comes back as an error naming
|
|
68
|
+
* them, which is the agent's cue to pick other work rather than retry.
|
|
69
|
+
*/
|
|
70
|
+
async function claimTask({ taskId, claimNote }, release) {
|
|
71
|
+
if (!taskId) throw new Error(`taskId is required to ${release ? 'release' : 'claim'} a task`);
|
|
72
|
+
return callZephlyAPI('mcpClaimTask', release ? { taskId, release: true } : { taskId, note: claimNote });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Suggested edits on a claimed task (E-259 #2809): list what is waiting, or
|
|
77
|
+
* answer one. The server decides who may accept, reject or withdraw.
|
|
78
|
+
*/
|
|
79
|
+
async function suggestedEdits({ taskId, editId, answer, note }, answering) {
|
|
80
|
+
if (!taskId) throw new Error('taskId is required');
|
|
81
|
+
if (!answering) return callZephlyAPI('mcpTaskSuggestedEdits', { taskId });
|
|
82
|
+
if (!editId || !answer) throw new Error('editId and answer (accept, reject or withdraw) are required');
|
|
83
|
+
return callZephlyAPI('mcpTaskSuggestedEdits', { taskId, editId, answer, note });
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
/**
|
|
62
87
|
* Bridge user terminology ("dashboard") to code locations by searching the
|
|
63
88
|
* manifest for the task's own words. Best-effort: returns null when there is
|
package/lib/create-server.js
CHANGED
|
@@ -20,6 +20,7 @@ import { listPrompts, getPromptContent } from '../prompts/index.js';
|
|
|
20
20
|
import { MCP_VERSION } from './version.js';
|
|
21
21
|
import { getLogger } from './logger.js';
|
|
22
22
|
import { isRemoteSafe } from './remote-tools.js';
|
|
23
|
+
import { annotate } from './tool-annotations.js';
|
|
23
24
|
import {
|
|
24
25
|
EMAIL_ALREADY_REGISTERED,
|
|
25
26
|
NOT_AUTHENTICATED,
|
|
@@ -84,7 +85,7 @@ export function createServer({ surface = 'local', startSignIn = defaultStartSign
|
|
|
84
85
|
// cannot disagree. They must agree: filtering only tools/list would leave
|
|
85
86
|
// every excluded handler dispatchable by a client that guesses the name,
|
|
86
87
|
// which is the failure this whole module exists to prevent.
|
|
87
|
-
const tools = surface === 'remote' ? TOOLS.filter((tool) => isRemoteSafe(tool.name)) : TOOLS;
|
|
88
|
+
const tools = (surface === 'remote' ? TOOLS.filter((tool) => isRemoteSafe(tool.name)) : TOOLS).map(annotate);
|
|
88
89
|
const available = new Set(tools.map((tool) => tool.name));
|
|
89
90
|
|
|
90
91
|
const server = new Server(
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool annotations: which tools only read (#2808).
|
|
3
|
+
*
|
|
4
|
+
* Without annotations a client must assume any tool may write or destroy data,
|
|
5
|
+
* which is what the MCP spec says an unannotated tool means. Claude uses the
|
|
6
|
+
* hint to sort a connector's tools into read-only and write/delete categories,
|
|
7
|
+
* so a policy that allows only read-only tools (Claude Team/Enterprise tool
|
|
8
|
+
* permissions) blocked EVERY tool here, reads included, while
|
|
9
|
+
* every tool was unannotated.
|
|
10
|
+
*
|
|
11
|
+
* A hint, not a control: the API enforces scopes and permissions whatever a
|
|
12
|
+
* client believes. So the list errs toward leaving a tool OUT. A write wrongly
|
|
13
|
+
* marked read-only would let it through a read-only policy, while a read left
|
|
14
|
+
* unmarked only costs the user an approval prompt.
|
|
15
|
+
*
|
|
16
|
+
* Membership was verified by tracing each handler to the endpoints it calls
|
|
17
|
+
* (config/endpoint-map.js): all GETs, or POSTs to query routes the API mounts
|
|
18
|
+
* behind OptionalReadScopeMiddleware (projects/story, organization/analyze,
|
|
19
|
+
* graph/*, tags/suggest, links/preview, attachments/download-url). Some have
|
|
20
|
+
* local side effects that change no EzModo data, and those count as reads:
|
|
21
|
+
* get_document caches the content into .ezmodo/docs, get_current_project_context
|
|
22
|
+
* and list_tags cache tags, and detect_git_repository and list_project_worktrees
|
|
23
|
+
* run read-only git commands.
|
|
24
|
+
*
|
|
25
|
+
* Adding a tool? If it only reads, add it here.
|
|
26
|
+
* __tests__/tool-annotations.test.js fails when a get_/list_/search_ tool is
|
|
27
|
+
* left unclassified, so a new read cannot silently default to "may write".
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export const READ_ONLY_TOOLS = new Set([
|
|
31
|
+
'detect_git_repository',
|
|
32
|
+
'estimate_task',
|
|
33
|
+
'evaluate_feature_flag',
|
|
34
|
+
'get_access',
|
|
35
|
+
'get_ai_insights',
|
|
36
|
+
'get_attachment_url',
|
|
37
|
+
'get_catalog',
|
|
38
|
+
'get_catalog_diff',
|
|
39
|
+
'get_context',
|
|
40
|
+
'get_current_project_context',
|
|
41
|
+
'get_decision',
|
|
42
|
+
'get_design',
|
|
43
|
+
'get_design_system',
|
|
44
|
+
'get_document',
|
|
45
|
+
'get_document_template',
|
|
46
|
+
'get_epic',
|
|
47
|
+
'get_epic_activity',
|
|
48
|
+
'get_epic_plan',
|
|
49
|
+
'get_feature',
|
|
50
|
+
'get_feature_flag',
|
|
51
|
+
'get_goal',
|
|
52
|
+
'get_graph',
|
|
53
|
+
'get_manifest_schema',
|
|
54
|
+
'get_milestone',
|
|
55
|
+
'get_org_areas',
|
|
56
|
+
'get_organization',
|
|
57
|
+
'get_project',
|
|
58
|
+
'get_project_changes',
|
|
59
|
+
'get_project_story',
|
|
60
|
+
'get_task',
|
|
61
|
+
'get_testing_summary',
|
|
62
|
+
'infer_dependencies',
|
|
63
|
+
'list_agent_suggestions',
|
|
64
|
+
'list_attachments',
|
|
65
|
+
'list_catalog_items',
|
|
66
|
+
'list_catalogs',
|
|
67
|
+
'list_designs',
|
|
68
|
+
'list_epic_comments',
|
|
69
|
+
'list_epics',
|
|
70
|
+
'list_facts',
|
|
71
|
+
'list_feature_flags',
|
|
72
|
+
'list_folders',
|
|
73
|
+
'list_links',
|
|
74
|
+
'list_notifications',
|
|
75
|
+
'list_org_documents',
|
|
76
|
+
'list_project_worktrees',
|
|
77
|
+
'list_repositories',
|
|
78
|
+
'list_tags',
|
|
79
|
+
'list_test_cases',
|
|
80
|
+
'list_test_suites',
|
|
81
|
+
'list_todos',
|
|
82
|
+
'list_unmapped_paths',
|
|
83
|
+
'list_watched',
|
|
84
|
+
'preview_links',
|
|
85
|
+
'resolve_concepts',
|
|
86
|
+
'search_epics',
|
|
87
|
+
'search_features',
|
|
88
|
+
'search_tasks',
|
|
89
|
+
'validate_manifest',
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The tool definition as a client should see it: read-only tools carry
|
|
94
|
+
* `annotations.readOnlyHint: true`; everything else is returned unchanged.
|
|
95
|
+
* Never mutates the shared definition in tools/.
|
|
96
|
+
*
|
|
97
|
+
* @template {{ name: string, annotations?: object }} T
|
|
98
|
+
* @param {T} tool
|
|
99
|
+
* @returns {T}
|
|
100
|
+
*/
|
|
101
|
+
export function annotate(tool) {
|
|
102
|
+
if (!READ_ONLY_TOOLS.has(tool.name)) return tool;
|
|
103
|
+
return { ...tool, annotations: { ...tool.annotations, readOnlyHint: true } };
|
|
104
|
+
}
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
package/tools/epics.js
CHANGED
|
@@ -56,8 +56,12 @@ export const EPIC_TOOLS = [
|
|
|
56
56
|
properties: {
|
|
57
57
|
action: {
|
|
58
58
|
type: 'string',
|
|
59
|
-
enum: ['create', 'update', 'generate_how_it_works', 'apply_how_it_works'],
|
|
60
|
-
description: 'Action to perform. "
|
|
59
|
+
enum: ['create', 'update', 'generate_how_it_works', 'apply_how_it_works', 'add_editor', 'remove_editor'],
|
|
60
|
+
description: 'Action to perform. "add_editor" / "remove_editor" (epicId + editorUserId) choose who ' +
|
|
61
|
+
'else may change the epic\'s plan, decide its questions and answer suggested changes (E-259). ' +
|
|
62
|
+
'Only the owner, the creator or an org admin may choose; an editor may remove themselves. ' +
|
|
63
|
+
'get_epic lists the current editors. ' +
|
|
64
|
+
'"generate_how_it_works" (re)generates the epic\'s grounded, ' +
|
|
61
65
|
'source-attributed "how it works" living description from its reality — its tasks\' status ' +
|
|
62
66
|
'rollup, their captured decisions and their linked commits (requires epicId; AI-quota gated). ' +
|
|
63
67
|
'An epic is where intent and reality drift furthest apart: intent is a charter written once, ' +
|
|
@@ -68,7 +72,12 @@ export const EPIC_TOOLS = [
|
|
|
68
72
|
// --- Identifiers ---
|
|
69
73
|
epicId: {
|
|
70
74
|
type: 'string',
|
|
71
|
-
description: 'Epic ID (required for update, generate_how_it_works, apply_how_it_works
|
|
75
|
+
description: 'Epic ID (required for update, generate_how_it_works, apply_how_it_works, ' +
|
|
76
|
+
'add_editor, remove_editor)',
|
|
77
|
+
},
|
|
78
|
+
editorUserId: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'add_editor / remove_editor: the user to add or remove (must be in the organization)',
|
|
72
81
|
},
|
|
73
82
|
markdown: {
|
|
74
83
|
type: 'string',
|
|
@@ -255,7 +264,8 @@ export const EPIC_TOOLS = [
|
|
|
255
264
|
'Responses include `descriptionDocumentId` — the id of the backing rich-description Document ' +
|
|
256
265
|
'when the description has been promoted to one (E-189), otherwise omitted. ' +
|
|
257
266
|
'Responses also include `derivedGoalIds` — the organization goal(s) this epic aligns to, ' +
|
|
258
|
-
'derived via its milestone (epics have no direct goal field).'
|
|
267
|
+
'derived via its milestone (epics have no direct goal field). ' +
|
|
268
|
+
'`editors` lists the people besides the owner who may change its plan (E-259).',
|
|
259
269
|
inputSchema: {
|
|
260
270
|
type: 'object',
|
|
261
271
|
properties: {
|
|
@@ -390,12 +400,18 @@ export const EPIC_TOOLS = [
|
|
|
390
400
|
name: 'get_epic_activity',
|
|
391
401
|
description: 'Catch me up on an epic (E-259): what changed since YOU last looked. Returns `summary`, ' +
|
|
392
402
|
'plain sentences you can relay to your person as-is, most important first: decisions waiting on ' +
|
|
393
|
-
'their view,
|
|
403
|
+
'their view, open questions put to them and open objections, comments that mention or reply to ' +
|
|
404
|
+
'them, decisions made, new plan versions (who ' +
|
|
394
405
|
'changed what, and which AI did it for them), and tasks added, started, finished or blocked. ' +
|
|
395
406
|
'The details are alongside. Their own changes are left out. Call it when you start or resume ' +
|
|
396
407
|
'work on an epic other people also work on, and before changing its plan. By default this also ' +
|
|
397
408
|
'marks the epic as caught up, so the next call shows only newer changes; pass markSeen:false to ' +
|
|
398
|
-
'look without that
|
|
409
|
+
'look without that.\n\n' +
|
|
410
|
+
'LIVE: pass waitSeconds (up to 25) to wait for something to happen instead of hearing ' +
|
|
411
|
+
'"nothing has changed" — the call returns as soon as someone changes the plan, comments, ' +
|
|
412
|
+
'decides, or picks up or moves a task. Call it again to keep following a shared session. ' +
|
|
413
|
+
'`hereNow` says who else is on the epic right now, people and their AIs; calling any epic tool ' +
|
|
414
|
+
'shows you there too.',
|
|
399
415
|
inputSchema: {
|
|
400
416
|
type: 'object',
|
|
401
417
|
properties: {
|
|
@@ -409,6 +425,10 @@ export const EPIC_TOOLS = [
|
|
|
409
425
|
type: 'boolean',
|
|
410
426
|
description: 'Mark the epic as caught up after answering (default true)',
|
|
411
427
|
},
|
|
428
|
+
waitSeconds: {
|
|
429
|
+
type: 'number',
|
|
430
|
+
description: 'Wait up to this many seconds (max 25) for something new before answering',
|
|
431
|
+
},
|
|
412
432
|
},
|
|
413
433
|
required: ['epicId'],
|
|
414
434
|
},
|
|
@@ -417,12 +437,15 @@ export const EPIC_TOOLS = [
|
|
|
417
437
|
name: 'list_epic_comments',
|
|
418
438
|
description: 'Read an epic\'s discussion (E-259), oldest first. Each comment says who wrote it and, ' +
|
|
419
439
|
'when an AI wrote it for them, which AI (`agentName`). Replies carry `parentId`. ' +
|
|
420
|
-
'Read this before planning or changing a shared epic: other people\'s questions and objections live here.'
|
|
440
|
+
'Read this before planning or changing a shared epic: other people\'s questions and objections live here. ' +
|
|
441
|
+
'Each comment has a `kind` (comment, question, objection, alternative); the last three stay open until ' +
|
|
442
|
+
'`resolvedAt` is set. Pass open:true for only the ones still waiting — check it before approving a plan.',
|
|
421
443
|
inputSchema: {
|
|
422
444
|
type: 'object',
|
|
423
445
|
properties: {
|
|
424
446
|
epicId: { type: 'string', description: 'The epic ID (required)' },
|
|
425
447
|
limit: { type: 'number', description: 'Maximum comments to return (default 100, max 500)' },
|
|
448
|
+
open: { type: 'boolean', description: 'Only the questions, objections and alternatives not yet settled' },
|
|
426
449
|
},
|
|
427
450
|
required: ['epicId'],
|
|
428
451
|
},
|
|
@@ -432,7 +455,11 @@ export const EPIC_TOOLS = [
|
|
|
432
455
|
description: 'Post to an epic\'s discussion (E-259), or reply to a comment with `parentId`. ' +
|
|
433
456
|
'Posted as the person whose key you use, marked as written by you. ' +
|
|
434
457
|
'Mentioned people, the author you reply to and everyone following the epic are notified, ' +
|
|
435
|
-
'and posting makes that person follow it. Write plainly: one point per comment, readable by anyone
|
|
458
|
+
'and posting makes that person follow it. Write plainly: one point per comment, readable by anyone.\n\n' +
|
|
459
|
+
'Say what the comment is with `kind`: a `question` you need answered, an `objection` to the plan, or ' +
|
|
460
|
+
'an `alternative` approach. Those stay open until settled, show up in catch me up, and an open objection ' +
|
|
461
|
+
'warns whoever approves the plan. To answer one, reply with parentId and resolvesParent:true ' +
|
|
462
|
+
'(the person who raised it, or the epic\'s owner, may settle it; anyone may reply).',
|
|
436
463
|
inputSchema: {
|
|
437
464
|
type: 'object',
|
|
438
465
|
properties: {
|
|
@@ -440,6 +467,15 @@ export const EPIC_TOOLS = [
|
|
|
440
467
|
content: { type: 'string', description: 'The comment (markdown)' },
|
|
441
468
|
parentId: { type: 'string', description: 'Reply to this comment' },
|
|
442
469
|
mentions: { type: 'array', items: { type: 'string' }, description: 'User IDs to notify' },
|
|
470
|
+
kind: {
|
|
471
|
+
type: 'string',
|
|
472
|
+
enum: ['comment', 'question', 'objection', 'alternative'],
|
|
473
|
+
description: 'What this is (default comment). Replies are always comments.',
|
|
474
|
+
},
|
|
475
|
+
resolvesParent: {
|
|
476
|
+
type: 'boolean',
|
|
477
|
+
description: 'With parentId: this reply settles the question, objection or alternative it answers',
|
|
478
|
+
},
|
|
443
479
|
},
|
|
444
480
|
required: ['epicId', 'content'],
|
|
445
481
|
},
|
|
@@ -453,7 +489,7 @@ export const EPIC_TOOLS = [
|
|
|
453
489
|
'take some and leave others. Nothing changes until they do.\n' +
|
|
454
490
|
'list: what is waiting on an epic. Open ones come first, and each change that no longer fits the ' +
|
|
455
491
|
'current plan is flagged with the reason.\n' +
|
|
456
|
-
'review (owner, creator or org admin only): `accept` and `reject` name changes by their op id. ' +
|
|
492
|
+
'review (owner, editors, creator or org admin only): `accept` and `reject` name changes by their op id. ' +
|
|
457
493
|
'A change you name in neither is left for later and the proposal stays open. A change whose task ' +
|
|
458
494
|
'someone has since removed is reported back as stale rather than quietly reapplied.\n' +
|
|
459
495
|
'withdraw: take back a proposal you made.',
|
package/tools/tasks.js
CHANGED
|
@@ -32,14 +32,51 @@ export const TASK_TOOLS = [
|
|
|
32
32
|
properties: {
|
|
33
33
|
action: {
|
|
34
34
|
type: 'string',
|
|
35
|
-
enum: [
|
|
36
|
-
|
|
35
|
+
enum: [
|
|
36
|
+
'create', 'update', 'complete', 'defer', 'link_commit', 'unlink_commit', 'get_commits',
|
|
37
|
+
'generate_how_it_works', 'apply_how_it_works', 'claim', 'release',
|
|
38
|
+
'list_suggested_edits', 'answer_suggested_edit',
|
|
39
|
+
],
|
|
40
|
+
description: 'Action to perform. "claim" (taskId, optional claimNote) says you are working on ' +
|
|
41
|
+
'this task, so other people\'s AIs on the same epic pick different work (E-259). Claim ' +
|
|
42
|
+
'BEFORE starting a task on a shared epic. It lasts 2 hours and renews while you update ' +
|
|
43
|
+
'the task or link commits; claiming again renews it. If someone else holds it you are ' +
|
|
44
|
+
'told who and which branch their work is on — pick another task. The answer warns about ' +
|
|
45
|
+
'other in-progress tasks on the epic that touch the same files, with the branch each is on, ' +
|
|
46
|
+
'so you can build on that work or keep clear of it. "release" gives it back when you stop. ' +
|
|
47
|
+
'TASK RULES: on a task someone ELSE has claimed, changing its title, description, steps or ' +
|
|
48
|
+
'epic, or deleting it, is sent to them as a suggested edit (the response says ' +
|
|
49
|
+
'`suggested: true`; anything else in the same update still applies), and changing its ' +
|
|
50
|
+
'status, assignee or step progress is refused — leave a comment instead. The claimer and ' +
|
|
51
|
+
'whoever runs the epic are not limited. "list_suggested_edits" (taskId) shows what is ' +
|
|
52
|
+
'waiting on a task; "answer_suggested_edit" (taskId, editId, answer: accept|reject|withdraw, ' +
|
|
53
|
+
'optional note) answers one — the claimer or whoever runs the epic accepts or rejects, the ' +
|
|
54
|
+
'author withdraws. ' +
|
|
55
|
+
'"generate_how_it_works" (re)generates the task\'s ' +
|
|
37
56
|
'grounded, source-attributed "how it works" living description from its reality — ' +
|
|
38
57
|
'subtasks, comments, status history, commits plus a manifest pass over linked files ' +
|
|
39
58
|
'(requires taskId; AI-quota gated). "apply_how_it_works" (BYO-AI) persists a summary ' +
|
|
40
59
|
'YOU authored: pass markdown + sources; the server validates your cited sources against ' +
|
|
41
60
|
'the real grounded context (dropping fabricated ones) before saving — no server model call.',
|
|
42
61
|
},
|
|
62
|
+
editId: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'answer_suggested_edit: the suggested edit to answer',
|
|
65
|
+
},
|
|
66
|
+
answer: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
enum: ['accept', 'reject', 'withdraw'],
|
|
69
|
+
description: 'answer_suggested_edit: accept or reject it (the claimer or whoever runs the epic), ' +
|
|
70
|
+
'or withdraw your own',
|
|
71
|
+
},
|
|
72
|
+
note: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
description: 'answer_suggested_edit: what you want to say back, optional',
|
|
75
|
+
},
|
|
76
|
+
claimNote: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'claim: what you are about to do, in a few words (shown to the others)',
|
|
79
|
+
},
|
|
43
80
|
// --- Identifiers (used by most actions) ---
|
|
44
81
|
taskId: {
|
|
45
82
|
type: 'string',
|