@vibescope/mcp-server 0.2.2 → 0.2.4
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/CHANGELOG.md +84 -0
- package/README.md +35 -20
- package/dist/api-client.d.ts +276 -8
- package/dist/api-client.js +128 -9
- package/dist/handlers/blockers.d.ts +11 -0
- package/dist/handlers/blockers.js +37 -2
- package/dist/handlers/bodies-of-work.d.ts +2 -0
- package/dist/handlers/bodies-of-work.js +30 -1
- package/dist/handlers/connectors.js +2 -2
- package/dist/handlers/decisions.d.ts +11 -0
- package/dist/handlers/decisions.js +37 -2
- package/dist/handlers/deployment.d.ts +6 -0
- package/dist/handlers/deployment.js +33 -5
- package/dist/handlers/discovery.js +27 -11
- package/dist/handlers/fallback.js +12 -6
- package/dist/handlers/file-checkouts.d.ts +1 -0
- package/dist/handlers/file-checkouts.js +17 -2
- package/dist/handlers/findings.d.ts +5 -0
- package/dist/handlers/findings.js +19 -2
- package/dist/handlers/git-issues.js +4 -2
- package/dist/handlers/ideas.d.ts +5 -0
- package/dist/handlers/ideas.js +19 -2
- package/dist/handlers/progress.js +2 -2
- package/dist/handlers/project.d.ts +1 -0
- package/dist/handlers/project.js +35 -2
- package/dist/handlers/requests.js +6 -3
- package/dist/handlers/roles.js +13 -2
- package/dist/handlers/session.d.ts +12 -0
- package/dist/handlers/session.js +288 -25
- package/dist/handlers/sprints.d.ts +2 -0
- package/dist/handlers/sprints.js +30 -1
- package/dist/handlers/tasks.d.ts +25 -2
- package/dist/handlers/tasks.js +228 -35
- package/dist/handlers/tool-docs.js +72 -5
- package/dist/templates/agent-guidelines.d.ts +18 -0
- package/dist/templates/agent-guidelines.js +207 -0
- package/dist/tools.js +478 -125
- package/dist/utils.d.ts +5 -2
- package/dist/utils.js +90 -51
- package/package.json +51 -46
- package/scripts/version-bump.ts +203 -0
- package/src/api-client.test.ts +8 -3
- package/src/api-client.ts +376 -13
- package/src/handlers/__test-setup__.ts +5 -0
- package/src/handlers/blockers.test.ts +76 -0
- package/src/handlers/blockers.ts +56 -2
- package/src/handlers/bodies-of-work.ts +59 -1
- package/src/handlers/connectors.ts +2 -2
- package/src/handlers/decisions.test.ts +71 -2
- package/src/handlers/decisions.ts +56 -2
- package/src/handlers/deployment.test.ts +81 -0
- package/src/handlers/deployment.ts +38 -5
- package/src/handlers/discovery.ts +27 -11
- package/src/handlers/fallback.test.ts +11 -10
- package/src/handlers/fallback.ts +14 -8
- package/src/handlers/file-checkouts.test.ts +83 -3
- package/src/handlers/file-checkouts.ts +22 -2
- package/src/handlers/findings.test.ts +2 -2
- package/src/handlers/findings.ts +38 -2
- package/src/handlers/git-issues.test.ts +2 -2
- package/src/handlers/git-issues.ts +4 -2
- package/src/handlers/ideas.test.ts +1 -1
- package/src/handlers/ideas.ts +34 -2
- package/src/handlers/progress.ts +2 -2
- package/src/handlers/project.ts +47 -2
- package/src/handlers/requests.test.ts +38 -7
- package/src/handlers/requests.ts +6 -3
- package/src/handlers/roles.test.ts +1 -1
- package/src/handlers/roles.ts +20 -2
- package/src/handlers/session.test.ts +303 -4
- package/src/handlers/session.ts +335 -28
- package/src/handlers/sprints.ts +61 -1
- package/src/handlers/tasks.test.ts +0 -73
- package/src/handlers/tasks.ts +269 -40
- package/src/handlers/tool-docs.ts +77 -5
- package/src/handlers/types.test.ts +259 -0
- package/src/templates/agent-guidelines.ts +210 -0
- package/src/tools.ts +479 -125
- package/src/utils.test.ts +7 -5
- package/src/utils.ts +95 -51
package/dist/handlers/session.js
CHANGED
|
@@ -8,11 +8,16 @@
|
|
|
8
8
|
* - get_help
|
|
9
9
|
* - get_token_usage
|
|
10
10
|
*/
|
|
11
|
+
import os from 'os';
|
|
11
12
|
import { parseArgs, createEnumValidator } from '../validators.js';
|
|
12
13
|
import { getApiClient } from '../api-client.js';
|
|
14
|
+
import { getAgentGuidelinesTemplate, getAgentGuidelinesSummary } from '../templates/agent-guidelines.js';
|
|
15
|
+
// Auto-detect machine hostname for worktree tracking
|
|
16
|
+
const MACHINE_HOSTNAME = os.hostname();
|
|
13
17
|
const VALID_MODES = ['lite', 'full'];
|
|
14
18
|
const VALID_MODELS = ['opus', 'sonnet', 'haiku'];
|
|
15
19
|
const VALID_ROLES = ['developer', 'validator', 'deployer', 'reviewer', 'maintainer'];
|
|
20
|
+
const VALID_AGENT_TYPES = ['claude', 'gemini', 'cursor', 'windsurf', 'other'];
|
|
16
21
|
// Argument schemas for type-safe parsing
|
|
17
22
|
const startWorkSessionSchema = {
|
|
18
23
|
project_id: { type: 'string' },
|
|
@@ -20,10 +25,13 @@ const startWorkSessionSchema = {
|
|
|
20
25
|
mode: { type: 'string', default: 'lite', validate: createEnumValidator(VALID_MODES) },
|
|
21
26
|
model: { type: 'string', validate: createEnumValidator(VALID_MODELS) },
|
|
22
27
|
role: { type: 'string', default: 'developer', validate: createEnumValidator(VALID_ROLES) },
|
|
28
|
+
hostname: { type: 'string' }, // Machine hostname for worktree tracking
|
|
29
|
+
agent_type: { type: 'string', validate: createEnumValidator(VALID_AGENT_TYPES) }, // Agent type for onboarding
|
|
23
30
|
};
|
|
24
31
|
const heartbeatSchema = {
|
|
25
32
|
session_id: { type: 'string' },
|
|
26
33
|
current_worktree_path: { type: 'string' },
|
|
34
|
+
hostname: { type: 'string' }, // Machine hostname for worktree tracking
|
|
27
35
|
};
|
|
28
36
|
const endWorkSessionSchema = {
|
|
29
37
|
session_id: { type: 'string' },
|
|
@@ -32,7 +40,9 @@ const getHelpSchema = {
|
|
|
32
40
|
topic: { type: 'string', required: true },
|
|
33
41
|
};
|
|
34
42
|
export const startWorkSession = async (args, ctx) => {
|
|
35
|
-
const { project_id, git_url, mode, model, role } = parseArgs(args, startWorkSessionSchema);
|
|
43
|
+
const { project_id, git_url, mode, model, role, hostname: providedHostname, agent_type } = parseArgs(args, startWorkSessionSchema);
|
|
44
|
+
// Use auto-detected hostname if not provided - enables machine-aware worktree filtering
|
|
45
|
+
const hostname = providedHostname || MACHINE_HOSTNAME;
|
|
36
46
|
const { session, updateSession } = ctx;
|
|
37
47
|
// Reset token tracking for new session with model info
|
|
38
48
|
const normalizedModel = model ? model.toLowerCase().replace(/^claude[- ]*/i, '') : null;
|
|
@@ -53,6 +63,9 @@ export const startWorkSession = async (args, ctx) => {
|
|
|
53
63
|
return {
|
|
54
64
|
result: {
|
|
55
65
|
error: 'Please provide project_id or git_url to start a session',
|
|
66
|
+
session_termination_required: true,
|
|
67
|
+
reason: 'Cannot start work without identifying a project',
|
|
68
|
+
action: 'END_SESSION_NOW - Do not proceed with any work until MCP is properly configured.',
|
|
56
69
|
},
|
|
57
70
|
};
|
|
58
71
|
}
|
|
@@ -62,18 +75,53 @@ export const startWorkSession = async (args, ctx) => {
|
|
|
62
75
|
git_url,
|
|
63
76
|
mode: mode,
|
|
64
77
|
model: model,
|
|
65
|
-
role: role
|
|
78
|
+
role: role,
|
|
79
|
+
hostname, // Machine hostname for worktree tracking
|
|
80
|
+
agent_type: agent_type, // Agent type for onboarding
|
|
66
81
|
});
|
|
67
82
|
if (!response.ok) {
|
|
83
|
+
// Include additional error details if available
|
|
84
|
+
const errorData = response.data;
|
|
68
85
|
return {
|
|
69
86
|
result: {
|
|
70
87
|
error: response.error || 'Failed to start session',
|
|
88
|
+
...(errorData?.detail && { detail: errorData.detail }),
|
|
89
|
+
...(errorData?.code && { code: errorData.code }),
|
|
90
|
+
session_termination_required: true,
|
|
91
|
+
reason: 'MCP server connection failed - cannot track work',
|
|
92
|
+
action: 'END_SESSION_NOW - Do not proceed with any work.',
|
|
93
|
+
troubleshooting: [
|
|
94
|
+
'1. Check if MCP server is configured: claude mcp list',
|
|
95
|
+
'2. Verify VIBESCOPE_API_KEY is set correctly',
|
|
96
|
+
'3. Check network connectivity to vibescope.dev',
|
|
97
|
+
'4. Restart Claude Code after fixing configuration',
|
|
98
|
+
],
|
|
99
|
+
user_message: 'MCP connection to Vibescope failed. I cannot proceed without task tracking. Please fix the configuration and restart.',
|
|
71
100
|
},
|
|
72
101
|
};
|
|
73
102
|
}
|
|
74
103
|
const data = response.data;
|
|
75
|
-
// Handle project not found
|
|
104
|
+
// Handle project not found - include agent guidelines for new project setup
|
|
76
105
|
if (!data?.session_started) {
|
|
106
|
+
// If project_not_found, include agent guidelines template for CLAUDE.md setup
|
|
107
|
+
if (data?.project_not_found) {
|
|
108
|
+
return {
|
|
109
|
+
result: {
|
|
110
|
+
...data,
|
|
111
|
+
agent_guidelines: {
|
|
112
|
+
message: 'IMPORTANT: After creating the project, add these guidelines to your .claude/CLAUDE.md file.',
|
|
113
|
+
summary: getAgentGuidelinesSummary(),
|
|
114
|
+
full_template: getAgentGuidelinesTemplate(),
|
|
115
|
+
setup_instructions: [
|
|
116
|
+
'1. Create the project using create_project()',
|
|
117
|
+
'2. Create .claude/CLAUDE.md in your project root',
|
|
118
|
+
'3. Copy the full_template content into CLAUDE.md',
|
|
119
|
+
'4. Call start_work_session again to begin work',
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
77
125
|
return { result: data };
|
|
78
126
|
}
|
|
79
127
|
// Store session ID and persona in local state
|
|
@@ -121,12 +169,28 @@ export const startWorkSession = async (args, ctx) => {
|
|
|
121
169
|
if (data.blockers_count !== undefined && data.blockers_count > 0) {
|
|
122
170
|
result.blockers_count = data.blockers_count;
|
|
123
171
|
}
|
|
124
|
-
// Add validation
|
|
172
|
+
// Add validation tasks when present - agents should validate before starting new work
|
|
125
173
|
if (data.validation_count !== undefined && data.validation_count > 0) {
|
|
126
174
|
result.validation_count = data.validation_count;
|
|
127
175
|
}
|
|
176
|
+
if (data.awaiting_validation && data.awaiting_validation.length > 0) {
|
|
177
|
+
result.awaiting_validation = data.awaiting_validation;
|
|
178
|
+
result.validation_priority = data.validation_priority;
|
|
179
|
+
}
|
|
180
|
+
// Add stale worktrees warning if any exist
|
|
181
|
+
if (data.stale_worktrees && data.stale_worktrees.length > 0) {
|
|
182
|
+
result.stale_worktrees = data.stale_worktrees;
|
|
183
|
+
result.stale_worktrees_count = data.stale_worktrees_count;
|
|
184
|
+
result.cleanup_action = data.cleanup_action;
|
|
185
|
+
}
|
|
128
186
|
// Add git workflow info if available in project
|
|
129
187
|
if (data.project?.git_workflow && data.project.git_workflow !== 'none') {
|
|
188
|
+
// Branching workflows (git-flow, github-flow) require worktrees
|
|
189
|
+
// Trunk-based development commits directly to main, no worktree needed
|
|
190
|
+
const isBranchingWorkflow = data.project.git_workflow === 'git-flow' || data.project.git_workflow === 'github-flow';
|
|
191
|
+
const baseBranch = data.project.git_workflow === 'git-flow'
|
|
192
|
+
? (data.project.git_develop_branch || 'develop')
|
|
193
|
+
: (data.project.git_main_branch || 'main');
|
|
130
194
|
result.git_workflow = {
|
|
131
195
|
workflow: data.project.git_workflow,
|
|
132
196
|
auto_branch: data.project.git_auto_branch ?? false,
|
|
@@ -134,17 +198,38 @@ export const startWorkSession = async (args, ctx) => {
|
|
|
134
198
|
...(data.project.git_workflow === 'git-flow' && data.project.git_develop_branch
|
|
135
199
|
? { develop_branch: data.project.git_develop_branch }
|
|
136
200
|
: {}),
|
|
137
|
-
worktree_required:
|
|
138
|
-
worktree_hint: 'CRITICAL: Create a git worktree before starting work. Run get_help("git") for instructions.',
|
|
201
|
+
worktree_required: isBranchingWorkflow,
|
|
139
202
|
};
|
|
203
|
+
// Only show worktree reminder for branching workflows (git-flow, github-flow)
|
|
204
|
+
if (isBranchingWorkflow) {
|
|
205
|
+
result.WORKTREE_REMINDER = {
|
|
206
|
+
message: 'CRITICAL: Create worktree BEFORE making ANY file edits',
|
|
207
|
+
wrong_order: 'DO NOT: Edit files → stash → create worktree → pop stash',
|
|
208
|
+
right_order: 'DO: Create worktree → cd into it → THEN edit files',
|
|
209
|
+
command: `git worktree add ../<project>-<persona>-<task> -b feature/<task-id> ${baseBranch}`,
|
|
210
|
+
help: 'Run get_help("git") for full instructions',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
140
213
|
}
|
|
141
|
-
// Add
|
|
214
|
+
// Add agent setup instructions if this is a new agent type for the project
|
|
215
|
+
if (data.agent_setup) {
|
|
216
|
+
result.agent_setup = data.agent_setup;
|
|
217
|
+
// If setup is required, update directive to prioritize setup
|
|
218
|
+
if (data.agent_setup.setup_required) {
|
|
219
|
+
result.directive = `SETUP REQUIRED: This is your first time connecting as a ${data.agent_setup.agent_type} agent. Follow the agent_setup instructions before starting work.`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Add next action at end - pending requests take priority over validation, then regular tasks
|
|
142
223
|
if (hasUrgentQuestions) {
|
|
143
224
|
const firstQuestion = data.URGENT_QUESTIONS?.requests?.[0] || data.pending_requests?.[0];
|
|
144
225
|
result.next_action = firstQuestion
|
|
145
226
|
? `answer_question(request_id: "${firstQuestion.id}", answer: "...")`
|
|
146
227
|
: 'Check pending_requests and respond using answer_question(request_id, answer)';
|
|
147
228
|
}
|
|
229
|
+
else if (data.awaiting_validation && data.awaiting_validation.length > 0) {
|
|
230
|
+
// Validation tasks take priority over new work - use next_action from API if available
|
|
231
|
+
result.next_action = data.next_action || `claim_validation(task_id: "${data.awaiting_validation[0].id}")`;
|
|
232
|
+
}
|
|
148
233
|
else if (data.next_task) {
|
|
149
234
|
result.next_action = `update_task(task_id: "${data.next_task.id}", status: "in_progress")`;
|
|
150
235
|
}
|
|
@@ -154,9 +239,11 @@ export const startWorkSession = async (args, ctx) => {
|
|
|
154
239
|
return { result };
|
|
155
240
|
};
|
|
156
241
|
export const heartbeat = async (args, ctx) => {
|
|
157
|
-
const { session_id, current_worktree_path } = parseArgs(args, heartbeatSchema);
|
|
242
|
+
const { session_id, current_worktree_path, hostname: providedHostname } = parseArgs(args, heartbeatSchema);
|
|
158
243
|
const { session } = ctx;
|
|
159
244
|
const targetSession = session_id || session.currentSessionId;
|
|
245
|
+
// Use auto-detected hostname if not provided
|
|
246
|
+
const hostname = providedHostname || MACHINE_HOSTNAME;
|
|
160
247
|
if (!targetSession) {
|
|
161
248
|
return {
|
|
162
249
|
result: {
|
|
@@ -165,9 +252,10 @@ export const heartbeat = async (args, ctx) => {
|
|
|
165
252
|
};
|
|
166
253
|
}
|
|
167
254
|
const apiClient = getApiClient();
|
|
168
|
-
// Send heartbeat with optional worktree path
|
|
255
|
+
// Send heartbeat with optional worktree path and hostname
|
|
169
256
|
const heartbeatResponse = await apiClient.heartbeat(targetSession, {
|
|
170
257
|
current_worktree_path,
|
|
258
|
+
hostname,
|
|
171
259
|
});
|
|
172
260
|
if (!heartbeatResponse.ok) {
|
|
173
261
|
return {
|
|
@@ -272,17 +360,38 @@ export const getHelp = async (args, _ctx) => {
|
|
|
272
360
|
}
|
|
273
361
|
return { result: { topic, content: response.data.content } };
|
|
274
362
|
};
|
|
275
|
-
// Model pricing rates (USD per 1M tokens)
|
|
276
363
|
const MODEL_PRICING = {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
364
|
+
standard: {
|
|
365
|
+
// Claude models
|
|
366
|
+
opus: { input: 15.0, output: 75.0, description: 'Claude Opus 4.5' },
|
|
367
|
+
sonnet: { input: 3.0, output: 15.0, description: 'Claude Sonnet 4' },
|
|
368
|
+
haiku: { input: 0.25, output: 1.25, description: 'Claude Haiku 3.5' },
|
|
369
|
+
// Gemini models (as of Jan 2025)
|
|
370
|
+
gemini: { input: 0.10, output: 0.40, description: 'Gemini 2.0 Flash' },
|
|
371
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40, description: 'Gemini 2.0 Flash' },
|
|
372
|
+
'gemini-1.5-pro': { input: 1.25, output: 5.00, description: 'Gemini 1.5 Pro' },
|
|
373
|
+
'gemini-1.5-flash': { input: 0.075, output: 0.30, description: 'Gemini 1.5 Flash' },
|
|
374
|
+
},
|
|
375
|
+
extra_usage: {
|
|
376
|
+
// Claude models - extra usage/overage rates (same as standard for now)
|
|
377
|
+
opus: { input: 15.0, output: 75.0, description: 'Claude Opus 4.5 - Extra usage' },
|
|
378
|
+
sonnet: { input: 3.0, output: 15.0, description: 'Claude Sonnet 4 - Extra usage' },
|
|
379
|
+
haiku: { input: 0.25, output: 1.25, description: 'Claude Haiku 3.5 - Extra usage' },
|
|
380
|
+
// Gemini models - extra usage rates (same as standard for now)
|
|
381
|
+
gemini: { input: 0.10, output: 0.40, description: 'Gemini 2.0 Flash - Extra usage' },
|
|
382
|
+
'gemini-2.0-flash': { input: 0.10, output: 0.40, description: 'Gemini 2.0 Flash - Extra usage' },
|
|
383
|
+
'gemini-1.5-pro': { input: 1.25, output: 5.00, description: 'Gemini 1.5 Pro - Extra usage' },
|
|
384
|
+
'gemini-1.5-flash': { input: 0.075, output: 0.30, description: 'Gemini 1.5 Flash - Extra usage' },
|
|
385
|
+
},
|
|
280
386
|
};
|
|
281
|
-
|
|
387
|
+
// Legacy accessor for backward compatibility
|
|
388
|
+
const getModelPricing = (tier = 'standard') => MODEL_PRICING[tier];
|
|
389
|
+
function calculateCost(byModel, tier = 'standard') {
|
|
282
390
|
const breakdown = {};
|
|
283
391
|
let total = 0;
|
|
392
|
+
const pricingTable = getModelPricing(tier);
|
|
284
393
|
for (const [model, tokens] of Object.entries(byModel)) {
|
|
285
|
-
const pricing =
|
|
394
|
+
const pricing = pricingTable[model];
|
|
286
395
|
if (pricing) {
|
|
287
396
|
const inputCost = (tokens.input / 1_000_000) * pricing.input;
|
|
288
397
|
const outputCost = (tokens.output / 1_000_000) * pricing.output;
|
|
@@ -291,11 +400,12 @@ function calculateCost(byModel) {
|
|
|
291
400
|
input_cost: Math.round(inputCost * 10000) / 10000,
|
|
292
401
|
output_cost: Math.round(outputCost * 10000) / 10000,
|
|
293
402
|
total: Math.round(modelTotal * 10000) / 10000,
|
|
403
|
+
description: pricing.description,
|
|
294
404
|
};
|
|
295
405
|
total += modelTotal;
|
|
296
406
|
}
|
|
297
407
|
}
|
|
298
|
-
return { breakdown, total: Math.round(total * 10000) / 10000 };
|
|
408
|
+
return { breakdown, total: Math.round(total * 10000) / 10000, pricing_tier: tier };
|
|
299
409
|
}
|
|
300
410
|
export const getTokenUsage = async (_args, ctx) => {
|
|
301
411
|
const { session } = ctx;
|
|
@@ -309,17 +419,19 @@ export const getTokenUsage = async (_args, ctx) => {
|
|
|
309
419
|
tokens: stats.tokens,
|
|
310
420
|
avg: Math.round(stats.tokens / stats.calls),
|
|
311
421
|
}));
|
|
312
|
-
// Calculate model breakdown and costs
|
|
422
|
+
// Calculate model breakdown and costs for both pricing tiers
|
|
313
423
|
const modelBreakdown = Object.entries(sessionTokenUsage.byModel || {}).map(([model, tokens]) => ({
|
|
314
424
|
model,
|
|
315
425
|
input_tokens: tokens.input,
|
|
316
426
|
output_tokens: tokens.output,
|
|
317
427
|
total_tokens: tokens.input + tokens.output,
|
|
318
428
|
}));
|
|
319
|
-
const
|
|
429
|
+
const standardCost = calculateCost(sessionTokenUsage.byModel || {}, 'standard');
|
|
430
|
+
const extraUsageCost = calculateCost(sessionTokenUsage.byModel || {}, 'extra_usage');
|
|
320
431
|
// If no model tracking, estimate cost assuming sonnet (middle tier)
|
|
321
|
-
const
|
|
322
|
-
|
|
432
|
+
const hasModelData = Object.keys(sessionTokenUsage.byModel || {}).length > 0;
|
|
433
|
+
const estimatedCostNoModel = !hasModelData
|
|
434
|
+
? Math.round((sessionTokenUsage.totalTokens / 1_000_000) * getModelPricing('standard').sonnet.output * 10000) / 10000
|
|
323
435
|
: null;
|
|
324
436
|
// Add context clearing directive when usage is high
|
|
325
437
|
const shouldClearContext = sessionTokenUsage.callCount > 50 || sessionTokenUsage.totalTokens > 100000;
|
|
@@ -336,19 +448,168 @@ export const getTokenUsage = async (_args, ctx) => {
|
|
|
336
448
|
top_tools: topTools,
|
|
337
449
|
model_breakdown: modelBreakdown.length > 0 ? modelBreakdown : undefined,
|
|
338
450
|
cost: {
|
|
339
|
-
|
|
340
|
-
|
|
451
|
+
// Standard tier (optimistic - included in Max plan)
|
|
452
|
+
standard: {
|
|
453
|
+
by_model: Object.keys(standardCost.breakdown).length > 0 ? standardCost.breakdown : undefined,
|
|
454
|
+
total_usd: standardCost.total > 0 ? standardCost.total : estimatedCostNoModel,
|
|
455
|
+
pricing_tier: 'standard',
|
|
456
|
+
},
|
|
457
|
+
// Extra usage tier (pessimistic - overage rates)
|
|
458
|
+
extra_usage: {
|
|
459
|
+
by_model: Object.keys(extraUsageCost.breakdown).length > 0 ? extraUsageCost.breakdown : undefined,
|
|
460
|
+
total_usd: extraUsageCost.total > 0 ? extraUsageCost.total : estimatedCostNoModel,
|
|
461
|
+
pricing_tier: 'extra_usage',
|
|
462
|
+
},
|
|
463
|
+
// Legacy field for backward compatibility
|
|
464
|
+
total_usd: standardCost.total > 0 ? standardCost.total : estimatedCostNoModel,
|
|
341
465
|
estimated: estimatedCostNoModel !== null,
|
|
342
466
|
},
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
: '
|
|
467
|
+
supported_models: {
|
|
468
|
+
claude: ['opus', 'sonnet', 'haiku'],
|
|
469
|
+
gemini: ['gemini', 'gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-1.5-flash'],
|
|
470
|
+
},
|
|
471
|
+
note: 'IMPORTANT: These estimates only track MCP tool I/O (~1-5% of actual API usage). Actual costs may be 20-100x higher due to conversation context, system prompts, and reasoning tokens. Use report_token_usage() with actual API response values for accurate tracking.',
|
|
472
|
+
estimation_coverage: 'mcp_tool_io_only',
|
|
346
473
|
...(shouldClearContext && {
|
|
347
474
|
directive: 'High token usage detected. Run /clear then start_work_session to reset context. Do not ask permission.',
|
|
348
475
|
}),
|
|
349
476
|
},
|
|
350
477
|
};
|
|
351
478
|
};
|
|
479
|
+
const reportTokenUsageSchema = {
|
|
480
|
+
input_tokens: { type: 'number', required: true },
|
|
481
|
+
output_tokens: { type: 'number', required: true },
|
|
482
|
+
model: { type: 'string', validate: createEnumValidator(VALID_MODELS) },
|
|
483
|
+
};
|
|
484
|
+
const confirmAgentSetupSchema = {
|
|
485
|
+
project_id: { type: 'string', required: true },
|
|
486
|
+
agent_type: { type: 'string', required: true, validate: createEnumValidator(VALID_AGENT_TYPES) },
|
|
487
|
+
};
|
|
488
|
+
/**
|
|
489
|
+
* Report actual Claude API token usage for accurate cost tracking.
|
|
490
|
+
* This allows agents to report their actual API usage instead of relying on MCP estimates.
|
|
491
|
+
* The backend will attribute costs to the current task if one is active.
|
|
492
|
+
*/
|
|
493
|
+
export const reportTokenUsage = async (args, ctx) => {
|
|
494
|
+
const { input_tokens, output_tokens, model } = parseArgs(args, reportTokenUsageSchema);
|
|
495
|
+
const { session, updateSession } = ctx;
|
|
496
|
+
// Validate token counts
|
|
497
|
+
if (input_tokens < 0 || output_tokens < 0) {
|
|
498
|
+
return {
|
|
499
|
+
result: {
|
|
500
|
+
error: 'Token counts must be non-negative',
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
// Determine which model to attribute to
|
|
505
|
+
const targetModel = model || session.tokenUsage.currentModel || 'sonnet';
|
|
506
|
+
// Update the session's local token usage
|
|
507
|
+
const updatedByModel = { ...session.tokenUsage.byModel };
|
|
508
|
+
if (!updatedByModel[targetModel]) {
|
|
509
|
+
updatedByModel[targetModel] = { input: 0, output: 0 };
|
|
510
|
+
}
|
|
511
|
+
updatedByModel[targetModel].input += input_tokens;
|
|
512
|
+
updatedByModel[targetModel].output += output_tokens;
|
|
513
|
+
const totalTokens = input_tokens + output_tokens;
|
|
514
|
+
updateSession({
|
|
515
|
+
tokenUsage: {
|
|
516
|
+
...session.tokenUsage,
|
|
517
|
+
callCount: session.tokenUsage.callCount + 1,
|
|
518
|
+
totalTokens: session.tokenUsage.totalTokens + totalTokens,
|
|
519
|
+
byModel: updatedByModel,
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
// Report to backend - this handles both session update and task cost attribution
|
|
523
|
+
const apiClient = getApiClient();
|
|
524
|
+
const currentSessionId = session.currentSessionId;
|
|
525
|
+
if (!currentSessionId) {
|
|
526
|
+
// Calculate cost locally if no session (use standard tier)
|
|
527
|
+
const pricing = getModelPricing('standard')[targetModel];
|
|
528
|
+
const inputCost = pricing ? (input_tokens / 1_000_000) * pricing.input : 0;
|
|
529
|
+
const outputCost = pricing ? (output_tokens / 1_000_000) * pricing.output : 0;
|
|
530
|
+
return {
|
|
531
|
+
result: {
|
|
532
|
+
success: true,
|
|
533
|
+
reported: {
|
|
534
|
+
model: targetModel,
|
|
535
|
+
input_tokens: input_tokens,
|
|
536
|
+
output_tokens: output_tokens,
|
|
537
|
+
total_tokens: totalTokens,
|
|
538
|
+
estimated_cost_usd: Math.round((inputCost + outputCost) * 10000) / 10000,
|
|
539
|
+
},
|
|
540
|
+
note: 'Token usage recorded locally. Start a session to attribute costs to your project.',
|
|
541
|
+
},
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
// Call the backend to report and attribute costs
|
|
545
|
+
const response = await apiClient.reportTokenUsage(currentSessionId, {
|
|
546
|
+
input_tokens: input_tokens,
|
|
547
|
+
output_tokens: output_tokens,
|
|
548
|
+
model: targetModel,
|
|
549
|
+
});
|
|
550
|
+
if (!response.ok) {
|
|
551
|
+
// Fall back to local calculation on error (use standard tier)
|
|
552
|
+
const pricing = getModelPricing('standard')[targetModel];
|
|
553
|
+
const inputCost = pricing ? (input_tokens / 1_000_000) * pricing.input : 0;
|
|
554
|
+
const outputCost = pricing ? (output_tokens / 1_000_000) * pricing.output : 0;
|
|
555
|
+
return {
|
|
556
|
+
result: {
|
|
557
|
+
success: true,
|
|
558
|
+
reported: {
|
|
559
|
+
model: targetModel,
|
|
560
|
+
input_tokens: input_tokens,
|
|
561
|
+
output_tokens: output_tokens,
|
|
562
|
+
total_tokens: totalTokens,
|
|
563
|
+
estimated_cost_usd: Math.round((inputCost + outputCost) * 10000) / 10000,
|
|
564
|
+
},
|
|
565
|
+
warning: 'Backend sync failed. Token usage recorded locally only.',
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
const data = response.data;
|
|
570
|
+
return {
|
|
571
|
+
result: {
|
|
572
|
+
success: true,
|
|
573
|
+
reported: data.reported,
|
|
574
|
+
task_attributed: data.task_attributed,
|
|
575
|
+
...(data.task_id && { task_id: data.task_id }),
|
|
576
|
+
note: data.task_attributed
|
|
577
|
+
? 'Token usage recorded and attributed to current task for per-task cost tracking.'
|
|
578
|
+
: 'Token usage recorded to session. No active task to attribute costs to.',
|
|
579
|
+
},
|
|
580
|
+
};
|
|
581
|
+
};
|
|
582
|
+
/**
|
|
583
|
+
* Confirm that agent setup is complete for a project.
|
|
584
|
+
* This marks the agent type as onboarded, so future sessions won't receive setup instructions.
|
|
585
|
+
*/
|
|
586
|
+
export const confirmAgentSetup = async (args, _ctx) => {
|
|
587
|
+
const { project_id, agent_type } = parseArgs(args, confirmAgentSetupSchema);
|
|
588
|
+
if (!project_id || !agent_type) {
|
|
589
|
+
return {
|
|
590
|
+
result: {
|
|
591
|
+
error: 'project_id and agent_type are required',
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const apiClient = getApiClient();
|
|
596
|
+
const response = await apiClient.confirmAgentSetup(project_id, agent_type);
|
|
597
|
+
if (!response.ok) {
|
|
598
|
+
return {
|
|
599
|
+
result: {
|
|
600
|
+
error: response.error || 'Failed to confirm agent setup',
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
result: {
|
|
606
|
+
success: true,
|
|
607
|
+
project_id,
|
|
608
|
+
agent_type,
|
|
609
|
+
message: `Setup confirmed for ${agent_type} agent. You will no longer receive setup instructions for this project.`,
|
|
610
|
+
},
|
|
611
|
+
};
|
|
612
|
+
};
|
|
352
613
|
/**
|
|
353
614
|
* Session handlers registry
|
|
354
615
|
*/
|
|
@@ -358,4 +619,6 @@ export const sessionHandlers = {
|
|
|
358
619
|
end_work_session: endWorkSession,
|
|
359
620
|
get_help: getHelp,
|
|
360
621
|
get_token_usage: getTokenUsage,
|
|
622
|
+
report_token_usage: reportTokenUsage,
|
|
623
|
+
confirm_agent_setup: confirmAgentSetup,
|
|
361
624
|
};
|
|
@@ -26,6 +26,8 @@ export declare const addTaskToSprint: Handler;
|
|
|
26
26
|
export declare const removeTaskFromSprint: Handler;
|
|
27
27
|
export declare const getSprintBacklog: Handler;
|
|
28
28
|
export declare const getSprintVelocity: Handler;
|
|
29
|
+
export declare const archiveSprint: Handler;
|
|
30
|
+
export declare const unarchiveSprint: Handler;
|
|
29
31
|
/**
|
|
30
32
|
* Sprint handlers registry
|
|
31
33
|
*/
|
package/dist/handlers/sprints.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { parseArgs, uuidValidator, createEnumValidator } from '../validators.js';
|
|
18
18
|
import { getApiClient } from '../api-client.js';
|
|
19
|
-
const SPRINT_STATUSES = ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled'];
|
|
19
|
+
const SPRINT_STATUSES = ['planning', 'active', 'in_review', 'retrospective', 'completed', 'cancelled', 'archived'];
|
|
20
20
|
const TASK_PHASES = ['pre', 'core', 'post'];
|
|
21
21
|
const DEPLOY_ENVIRONMENTS = ['development', 'staging', 'production'];
|
|
22
22
|
const VERSION_BUMPS = ['patch', 'minor', 'major'];
|
|
@@ -269,6 +269,33 @@ export const getSprintVelocity = async (args, ctx) => {
|
|
|
269
269
|
}
|
|
270
270
|
return { result: response.data };
|
|
271
271
|
};
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// Archive / Unarchive Handlers
|
|
274
|
+
// ============================================================================
|
|
275
|
+
const archiveSprintSchema = {
|
|
276
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
277
|
+
};
|
|
278
|
+
const unarchiveSprintSchema = {
|
|
279
|
+
sprint_id: { type: 'string', required: true, validate: uuidValidator },
|
|
280
|
+
};
|
|
281
|
+
export const archiveSprint = async (args, ctx) => {
|
|
282
|
+
const { sprint_id } = parseArgs(args, archiveSprintSchema);
|
|
283
|
+
const apiClient = getApiClient();
|
|
284
|
+
const response = await apiClient.proxy('archive_sprint', { sprint_id });
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
return { result: { error: response.error || 'Failed to archive sprint' }, isError: true };
|
|
287
|
+
}
|
|
288
|
+
return { result: response.data };
|
|
289
|
+
};
|
|
290
|
+
export const unarchiveSprint = async (args, ctx) => {
|
|
291
|
+
const { sprint_id } = parseArgs(args, unarchiveSprintSchema);
|
|
292
|
+
const apiClient = getApiClient();
|
|
293
|
+
const response = await apiClient.proxy('unarchive_sprint', { sprint_id });
|
|
294
|
+
if (!response.ok) {
|
|
295
|
+
return { result: { error: response.error || 'Failed to unarchive sprint' }, isError: true };
|
|
296
|
+
}
|
|
297
|
+
return { result: response.data };
|
|
298
|
+
};
|
|
272
299
|
/**
|
|
273
300
|
* Sprint handlers registry
|
|
274
301
|
*/
|
|
@@ -284,4 +311,6 @@ export const sprintHandlers = {
|
|
|
284
311
|
remove_task_from_sprint: removeTaskFromSprint,
|
|
285
312
|
get_sprint_backlog: getSprintBacklog,
|
|
286
313
|
get_sprint_velocity: getSprintVelocity,
|
|
314
|
+
archive_sprint: archiveSprint,
|
|
315
|
+
unarchive_sprint: unarchiveSprint,
|
|
287
316
|
};
|
package/dist/handlers/tasks.d.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Task Handlers (Migrated to API Client)
|
|
3
3
|
*
|
|
4
4
|
* Handles task CRUD and management:
|
|
5
|
-
* -
|
|
5
|
+
* - get_task (single task by ID)
|
|
6
|
+
* - search_tasks (text search)
|
|
7
|
+
* - get_tasks_by_priority (priority filter)
|
|
8
|
+
* - get_recent_tasks (by date)
|
|
9
|
+
* - get_task_stats (aggregate counts)
|
|
6
10
|
* - get_next_task
|
|
7
11
|
* - add_task
|
|
8
12
|
* - update_task
|
|
@@ -30,7 +34,6 @@ interface GitMergeInstructions {
|
|
|
30
34
|
note: string;
|
|
31
35
|
}
|
|
32
36
|
export declare function getValidationApprovedGitInstructions(config: GitWorkflowConfig, taskBranch: string | undefined): GitMergeInstructions | undefined;
|
|
33
|
-
export declare const getTasks: Handler;
|
|
34
37
|
export declare const getNextTask: Handler;
|
|
35
38
|
export declare const addTask: Handler;
|
|
36
39
|
export declare const updateTask: Handler;
|
|
@@ -42,6 +45,26 @@ export declare const batchUpdateTasks: Handler;
|
|
|
42
45
|
export declare const batchCompleteTasks: Handler;
|
|
43
46
|
export declare const addSubtask: Handler;
|
|
44
47
|
export declare const getSubtasks: Handler;
|
|
48
|
+
/**
|
|
49
|
+
* Get a single task by ID with optional subtasks and milestones
|
|
50
|
+
*/
|
|
51
|
+
export declare const getTask: Handler;
|
|
52
|
+
/**
|
|
53
|
+
* Search tasks by text query with pagination
|
|
54
|
+
*/
|
|
55
|
+
export declare const searchTasks: Handler;
|
|
56
|
+
/**
|
|
57
|
+
* Get tasks filtered by priority with pagination
|
|
58
|
+
*/
|
|
59
|
+
export declare const getTasksByPriority: Handler;
|
|
60
|
+
/**
|
|
61
|
+
* Get recent tasks (newest or oldest) with pagination
|
|
62
|
+
*/
|
|
63
|
+
export declare const getRecentTasks: Handler;
|
|
64
|
+
/**
|
|
65
|
+
* Get task statistics for a project (aggregate counts only, minimal tokens)
|
|
66
|
+
*/
|
|
67
|
+
export declare const getTaskStats: Handler;
|
|
45
68
|
export declare const getStaleWorktrees: Handler;
|
|
46
69
|
export declare const clearWorktreePath: Handler;
|
|
47
70
|
/**
|