@mgsoftwarebv/mcp-server-bridge 3.3.8 → 3.4.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/dist/index.js CHANGED
@@ -103488,16 +103488,6 @@ async function getUserAccessibleCustomerIds(userId) {
103488
103488
  }
103489
103489
  return [...all];
103490
103490
  }
103491
- async function resolveAiSessionId(prefix, teamIds) {
103492
- if (teamIds.length === 0) return null;
103493
- const rows = await db.select({ id: aiSessions.id }).from(aiSessions).where(
103494
- and(
103495
- teamIds.length === 1 ? eq(aiSessions.teamId, teamIds[0]) : sql`${aiSessions.teamId} = ANY(${teamIds}::uuid[])`,
103496
- sql`${aiSessions.id}::text LIKE ${`${prefix}%`}`
103497
- )
103498
- ).limit(1);
103499
- return rows[0]?.id ?? null;
103500
- }
103501
103491
 
103502
103492
  // src/auth.ts
103503
103493
  var authStorage = new AsyncLocalStorage();
@@ -105378,10 +105368,6 @@ var Server = class extends Protocol {
105378
105368
  function asToolArgs(input) {
105379
105369
  return input ?? {};
105380
105370
  }
105381
- function roundToNearest15Minutes(minutes) {
105382
- if (minutes <= 0) return 0;
105383
- return Math.round(minutes / 15) * 15;
105384
- }
105385
105371
  function buildTicketAccessPredicate(teamIds, projectIds, customerIds) {
105386
105372
  const branches = [];
105387
105373
  if (teamIds.length > 0) branches.push(inArray(schema_exports.tickets.teamId, teamIds));
@@ -106291,242 +106277,6 @@ var TOOLS = [
106291
106277
  required: ["documentId", "invoiceId"]
106292
106278
  }
106293
106279
  },
106294
- {
106295
- name: "start-ai-session-smart",
106296
- description: "Start a new AI development session with automatic tracking",
106297
- inputSchema: {
106298
- type: "object",
106299
- properties: {
106300
- teamId: teamIdProp,
106301
- ticketId: { type: "string" },
106302
- ticketUrl: { type: "string", description: "URL to the ticket" },
106303
- cursorSessionId: {
106304
- type: "string",
106305
- description: "Cursor session identifier"
106306
- },
106307
- totalEstimatedMinutes: {
106308
- type: "number",
106309
- description: "Total estimated time in minutes (senior dev WITHOUT AI, rounded to 15 min)"
106310
- },
106311
- complexityScore: {
106312
- type: "number",
106313
- minimum: 1,
106314
- maximum: 10,
106315
- description: "Estimated complexity from 1-10"
106316
- }
106317
- },
106318
- required: ["ticketId", "totalEstimatedMinutes"]
106319
- }
106320
- },
106321
- {
106322
- name: "track-manual-follow-up",
106323
- description: "Track manual follow-up prompt by developer",
106324
- inputSchema: {
106325
- type: "object",
106326
- properties: {
106327
- teamId: teamIdProp,
106328
- aiSessionId: { type: "string" },
106329
- originalPrompt: { type: "string" },
106330
- aiResponse: { type: "string" },
106331
- developerFollowUp: { type: "string" },
106332
- followUpReason: {
106333
- type: "string",
106334
- enum: [
106335
- "incomplete_result",
106336
- "wrong_approach",
106337
- "needs_clarification",
106338
- "error_in_code"
106339
- ]
106340
- },
106341
- outcome: {
106342
- type: "string",
106343
- enum: ["success", "partial_success", "still_failed"],
106344
- default: "success"
106345
- },
106346
- estimatedMinutes: {
106347
- type: "number",
106348
- description: "Estimated time needed for this follow-up work (think as senior dev WITHOUT AI, in minutes)"
106349
- },
106350
- workDescription: {
106351
- type: "string",
106352
- description: "Detailed work description generated by AI (2-3 sentences, summarizing all work done in session including follow-ups)"
106353
- }
106354
- },
106355
- required: [
106356
- "aiSessionId",
106357
- "originalPrompt",
106358
- "aiResponse",
106359
- "developerFollowUp",
106360
- "followUpReason",
106361
- "estimatedMinutes",
106362
- "workDescription"
106363
- ]
106364
- }
106365
- },
106366
- {
106367
- name: "get-session-context",
106368
- description: "Get current session context for follow-up continuity",
106369
- inputSchema: {
106370
- type: "object",
106371
- properties: {
106372
- teamId: teamIdProp,
106373
- aiSessionId: { type: "string" },
106374
- includeTicketData: { type: "boolean", default: true },
106375
- includeTodoProgress: { type: "boolean", default: true },
106376
- includeFollowUpHistory: { type: "boolean", default: false }
106377
- },
106378
- required: ["aiSessionId"]
106379
- }
106380
- },
106381
- {
106382
- name: "sync-session-todos",
106383
- description: "Synchronize todo list with AI session (replace existing) or add new todos",
106384
- inputSchema: {
106385
- type: "object",
106386
- properties: {
106387
- teamId: teamIdProp,
106388
- aiSessionId: { type: "string" },
106389
- todos: {
106390
- type: "array",
106391
- items: {
106392
- type: "object",
106393
- properties: {
106394
- todoId: {
106395
- type: "string",
106396
- description: "Optional external todo ID for tracking"
106397
- },
106398
- content: { type: "string" },
106399
- status: {
106400
- type: "string",
106401
- enum: ["pending", "in_progress", "completed", "cancelled"]
106402
- },
106403
- estimatedMinutes: { type: "number" }
106404
- },
106405
- required: ["content", "status"]
106406
- }
106407
- },
106408
- replaceAll: {
106409
- type: "boolean",
106410
- default: true,
106411
- description: "If true, replace all existing todos. If false, add new todos to existing ones"
106412
- }
106413
- },
106414
- required: ["aiSessionId", "todos"]
106415
- }
106416
- },
106417
- {
106418
- name: "add-follow-up-todos",
106419
- description: "Add new todos from follow-up (without replacing existing ones)",
106420
- inputSchema: {
106421
- type: "object",
106422
- properties: {
106423
- teamId: teamIdProp,
106424
- aiSessionId: { type: "string" },
106425
- newTodos: {
106426
- type: "array",
106427
- items: {
106428
- type: "object",
106429
- properties: {
106430
- content: { type: "string" },
106431
- status: {
106432
- type: "string",
106433
- enum: ["pending", "in_progress"],
106434
- default: "pending"
106435
- },
106436
- estimatedMinutes: { type: "number" },
106437
- addedInFollowUp: { type: "boolean", default: true }
106438
- },
106439
- required: ["content"]
106440
- }
106441
- },
106442
- followUpReason: {
106443
- type: "string",
106444
- description: "Why were these todos added in follow-up"
106445
- }
106446
- },
106447
- required: ["aiSessionId", "newTodos"]
106448
- }
106449
- },
106450
- {
106451
- name: "update-session-status",
106452
- description: "Update AI session status and completion info",
106453
- inputSchema: {
106454
- type: "object",
106455
- properties: {
106456
- teamId: teamIdProp,
106457
- aiSessionId: { type: "string" },
106458
- status: {
106459
- type: "string",
106460
- enum: ["started", "in_progress", "paused", "completed", "failed"]
106461
- },
106462
- actualTimeMinutes: { type: "number" },
106463
- completionNotes: { type: "string" }
106464
- },
106465
- required: ["aiSessionId", "status"]
106466
- }
106467
- },
106468
- {
106469
- name: "get-completion-context",
106470
- description: "Get all context needed for Cursor AI to generate customer response",
106471
- inputSchema: {
106472
- type: "object",
106473
- properties: {
106474
- teamId: teamIdProp,
106475
- aiSessionId: { type: "string" },
106476
- includeFollowUps: { type: "boolean", default: true },
106477
- includeTimeMetrics: { type: "boolean", default: true },
106478
- includeTodos: { type: "boolean", default: true }
106479
- },
106480
- required: ["aiSessionId"]
106481
- }
106482
- },
106483
- {
106484
- name: "save-customer-response",
106485
- description: "Save customer response generated by Cursor AI",
106486
- inputSchema: {
106487
- type: "object",
106488
- properties: {
106489
- teamId: teamIdProp,
106490
- aiSessionId: { type: "string" },
106491
- customerResponse: {
106492
- type: "string",
106493
- description: "Customer response generated by Cursor AI"
106494
- },
106495
- responseType: {
106496
- type: "string",
106497
- enum: ["completion", "progress_update", "needs_clarification"],
106498
- default: "completion"
106499
- }
106500
- },
106501
- required: ["aiSessionId", "customerResponse"]
106502
- }
106503
- },
106504
- {
106505
- name: "complete-ai-session",
106506
- description: "Complete AI session with work summary - time calculated automatically",
106507
- inputSchema: {
106508
- type: "object",
106509
- properties: {
106510
- teamId: teamIdProp,
106511
- aiSessionId: { type: "string" },
106512
- workCompleted: {
106513
- type: "array",
106514
- items: { type: "string" },
106515
- description: "List of completed tasks/todos in English"
106516
- },
106517
- technicalSummary: {
106518
- type: "string",
106519
- description: "Technical summary of work done in English"
106520
- },
106521
- invoiceDescription: {
106522
- type: "string",
106523
- description: "Short invoice-friendly description in the language of the ticket (2-3 sentences max, suitable for billing)"
106524
- },
106525
- efficiencyNotes: { type: "string" }
106526
- },
106527
- required: ["aiSessionId", "workCompleted"]
106528
- }
106529
- },
106530
106280
  {
106531
106281
  name: "log-hours",
106532
106282
  description: "Analyze current chat conversation and log hours as draft tracker entry. AI analyzes chat context to estimate hours as a senior developer would (without AI assistance). Cursor AI matches workspace name to correct project from list (optional).",
@@ -106542,10 +106292,6 @@ var TOOLS = [
106542
106292
  type: "string",
106543
106293
  description: "Ticket ID (UUID) - Optional. Cursor AI should call get-tickets (filtered by project) to try matching chat context to an open ticket. Only include if a clear match is found."
106544
106294
  },
106545
- aiSessionId: {
106546
- type: "string",
106547
- description: "AI Session ID - Optional. If a ticket has an active AI dev session, include this ID to link the hours to that session."
106548
- },
106549
106295
  workDescription: {
106550
106296
  type: "string",
106551
106297
  description: "Short description of the work done (for the tracker entry)"
@@ -112400,7 +112146,6 @@ async function handleLogHours(input) {
112400
112146
  const {
112401
112147
  projectId,
112402
112148
  ticketId,
112403
- aiSessionId,
112404
112149
  workDescription,
112405
112150
  estimatedHours,
112406
112151
  chatContextSummary
@@ -112409,7 +112154,6 @@ async function handleLogHours(input) {
112409
112154
  if (!scope.ok) return scope.response;
112410
112155
  let project = null;
112411
112156
  let ticket = null;
112412
- let aiSession = null;
112413
112157
  if (projectId) {
112414
112158
  if (!scope.projectIds.includes(projectId)) {
112415
112159
  throw new Error(
@@ -112451,15 +112195,6 @@ async function handleLogHours(input) {
112451
112195
  }
112452
112196
  ticket = ticketData;
112453
112197
  }
112454
- if (aiSessionId) {
112455
- const [sessionData] = await db.select({
112456
- id: schema_exports.aiSessions.id,
112457
- ticketId: schema_exports.aiSessions.ticketId,
112458
- status: schema_exports.aiSessions.status
112459
- }).from(schema_exports.aiSessions).where(eq(schema_exports.aiSessions.id, aiSessionId)).limit(1);
112460
- if (!sessionData) throw new Error(`AI Session not found: ${aiSessionId}.`);
112461
- aiSession = sessionData;
112462
- }
112463
112198
  let insertTeamId = ticket?.teamId ?? project?.teamId ?? null;
112464
112199
  if (!insertTeamId) {
112465
112200
  const resolved = await resolveTeamId(input.teamId);
@@ -112471,40 +112206,24 @@ async function handleLogHours(input) {
112471
112206
  let agendaEntry = null;
112472
112207
  let wasUpdated = false;
112473
112208
  let consolidatedCount = 0;
112474
- if (aiSession?.id || ticket?.id) {
112209
+ if (ticket?.id) {
112475
112210
  let existingEntries = [];
112476
- if (aiSession?.id) {
112211
+ const linkedEvents = await db.select({
112212
+ timesheetEventId: schema_exports.timesheetEventTickets.timesheetEventId
112213
+ }).from(schema_exports.timesheetEventTickets).where(eq(schema_exports.timesheetEventTickets.ticketId, ticket.id));
112214
+ const eventIds = linkedEvents.map((e6) => e6.timesheetEventId);
112215
+ if (eventIds.length > 0) {
112477
112216
  existingEntries = await db.select({
112478
112217
  id: schema_exports.timesheetEvents.id,
112479
112218
  trackedDuration: schema_exports.timesheetEvents.trackedDuration,
112480
- projectId: schema_exports.timesheetEvents.projectId,
112481
- aiSessionId: schema_exports.timesheetEvents.aiSessionId
112219
+ projectId: schema_exports.timesheetEvents.projectId
112482
112220
  }).from(schema_exports.timesheetEvents).where(
112483
112221
  and(
112222
+ inArray(schema_exports.timesheetEvents.id, eventIds),
112484
112223
  eq(schema_exports.timesheetEvents.status, "draft"),
112485
- eq(schema_exports.timesheetEvents.userId, ctx.userId),
112486
- eq(schema_exports.timesheetEvents.aiSessionId, aiSession.id)
112224
+ eq(schema_exports.timesheetEvents.userId, ctx.userId)
112487
112225
  )
112488
112226
  ).orderBy(desc(schema_exports.timesheetEvents.createdAt));
112489
- } else if (ticket?.id) {
112490
- const linkedEvents = await db.select({
112491
- timesheetEventId: schema_exports.timesheetEventTickets.timesheetEventId
112492
- }).from(schema_exports.timesheetEventTickets).where(eq(schema_exports.timesheetEventTickets.ticketId, ticket.id));
112493
- const eventIds = linkedEvents.map((e6) => e6.timesheetEventId);
112494
- if (eventIds.length > 0) {
112495
- existingEntries = await db.select({
112496
- id: schema_exports.timesheetEvents.id,
112497
- trackedDuration: schema_exports.timesheetEvents.trackedDuration,
112498
- projectId: schema_exports.timesheetEvents.projectId,
112499
- aiSessionId: schema_exports.timesheetEvents.aiSessionId
112500
- }).from(schema_exports.timesheetEvents).where(
112501
- and(
112502
- inArray(schema_exports.timesheetEvents.id, eventIds),
112503
- eq(schema_exports.timesheetEvents.status, "draft"),
112504
- eq(schema_exports.timesheetEvents.userId, ctx.userId)
112505
- )
112506
- ).orderBy(desc(schema_exports.timesheetEvents.createdAt));
112507
- }
112508
112227
  }
112509
112228
  if (existingEntries.length > 0) {
112510
112229
  const existingEntry = existingEntries[0];
@@ -112523,8 +112242,7 @@ async function handleLogHours(input) {
112523
112242
  }).where(eq(schema_exports.timesheetEvents.id, existingEntry.id)).returning({
112524
112243
  id: schema_exports.timesheetEvents.id,
112525
112244
  trackedDuration: schema_exports.timesheetEvents.trackedDuration,
112526
- projectId: schema_exports.timesheetEvents.projectId,
112527
- aiSessionId: schema_exports.timesheetEvents.aiSessionId
112245
+ projectId: schema_exports.timesheetEvents.projectId
112528
112246
  });
112529
112247
  agendaEntry = updated ?? null;
112530
112248
  wasUpdated = true;
@@ -112536,7 +112254,6 @@ async function handleLogHours(input) {
112536
112254
  teamId: insertTeamId,
112537
112255
  userId: ctx.userId,
112538
112256
  projectId: project?.id ?? null,
112539
- aiSessionId: aiSession?.id ?? null,
112540
112257
  title: workDescription,
112541
112258
  description: chatContextSummary ?? workDescription,
112542
112259
  startTime: startTime.toISOString(),
@@ -112549,8 +112266,7 @@ async function handleLogHours(input) {
112549
112266
  }).returning({
112550
112267
  id: schema_exports.timesheetEvents.id,
112551
112268
  trackedDuration: schema_exports.timesheetEvents.trackedDuration,
112552
- projectId: schema_exports.timesheetEvents.projectId,
112553
- aiSessionId: schema_exports.timesheetEvents.aiSessionId
112269
+ projectId: schema_exports.timesheetEvents.projectId
112554
112270
  });
112555
112271
  agendaEntry = created ?? null;
112556
112272
  if (agendaEntry && ticket?.id) {
@@ -112581,9 +112297,6 @@ async function handleLogHours(input) {
112581
112297
  `;
112582
112298
  if (ticket)
112583
112299
  responseText += ` \u2022 Ticket: ${ticket.title} (${ticket.status})
112584
- `;
112585
- if (aiSession)
112586
- responseText += ` \u2022 AI Session: ${aiSession.id} (${aiSession.status})
112587
112300
  `;
112588
112301
  responseText += ` \u2022 Description: ${workDescription}
112589
112302
  `;
@@ -113292,870 +113005,6 @@ async function handleRemoveProjectMember(input) {
113292
113005
  return textResponse(text3);
113293
113006
  }
113294
113007
 
113295
- // src/tools/session-completion.ts
113296
- async function handleGetCompletionContext(input) {
113297
- const {
113298
- aiSessionId,
113299
- includeFollowUps = true,
113300
- includeTimeMetrics = true,
113301
- includeTodos = true
113302
- } = input;
113303
- const scope = await resolveTeamScope(input.teamId);
113304
- if (!scope.ok) return scope.response;
113305
- const prefix = aiSessionId.replace("ai-sess-", "");
113306
- const fullSessionId = await resolveAiSessionId(prefix, scope.teamIds);
113307
- if (!fullSessionId) throw new Error(`Session not found: ${aiSessionId}`);
113308
- const [session] = await db.select({
113309
- id: schema_exports.aiSessions.id,
113310
- ticketId: schema_exports.aiSessions.ticketId,
113311
- aiTimeEstimateMinutes: schema_exports.aiSessions.aiTimeEstimateMinutes,
113312
- actualTimeMinutes: schema_exports.aiSessions.actualTimeMinutes,
113313
- efficiencyScore: schema_exports.aiSessions.efficiencyScore,
113314
- createdAt: schema_exports.aiSessions.createdAt,
113315
- completedAt: schema_exports.aiSessions.completedAt,
113316
- status: schema_exports.aiSessions.status,
113317
- complexityScore: schema_exports.aiSessions.complexityScore
113318
- }).from(schema_exports.aiSessions).where(eq(schema_exports.aiSessions.id, fullSessionId)).limit(1);
113319
- if (!session) throw new Error(`Session not found: ${aiSessionId}`);
113320
- const [ticket] = await db.select({
113321
- ticketNumber: schema_exports.tickets.ticketNumber,
113322
- title: schema_exports.tickets.title,
113323
- description: schema_exports.tickets.description,
113324
- type: schema_exports.tickets.type,
113325
- priority: schema_exports.tickets.priority
113326
- }).from(schema_exports.tickets).where(eq(schema_exports.tickets.id, session.ticketId)).limit(1);
113327
- if (!ticket) throw new Error("Ticket not found for session");
113328
- const contextData = {
113329
- session: {
113330
- id: aiSessionId,
113331
- status: session.status,
113332
- complexity: session.complexityScore,
113333
- createdAt: session.createdAt,
113334
- completedAt: session.completedAt
113335
- },
113336
- ticket: {
113337
- number: ticket.ticketNumber,
113338
- title: ticket.title,
113339
- description: ticket.description,
113340
- type: ticket.type,
113341
- priority: ticket.priority
113342
- }
113343
- };
113344
- if (includeTimeMetrics) {
113345
- const timeSaved = session.aiTimeEstimateMinutes && session.actualTimeMinutes ? Math.max(0, session.aiTimeEstimateMinutes - session.actualTimeMinutes) : null;
113346
- contextData.timeMetrics = {
113347
- estimatedMinutes: session.aiTimeEstimateMinutes,
113348
- actualMinutes: session.actualTimeMinutes,
113349
- timeSaved,
113350
- efficiency: session.efficiencyScore,
113351
- sessionDuration: session.completedAt && session.createdAt ? Math.round(
113352
- (new Date(session.completedAt).getTime() - new Date(session.createdAt).getTime()) / 6e4
113353
- ) : null
113354
- };
113355
- }
113356
- if (includeTodos) {
113357
- const todos3 = await db.select({
113358
- content: schema_exports.aiTodos.content,
113359
- status: schema_exports.aiTodos.status,
113360
- estimatedMinutes: schema_exports.aiTodos.estimatedMinutes,
113361
- actualMinutes: schema_exports.aiTodos.actualMinutes,
113362
- completedAt: schema_exports.aiTodos.completedAt
113363
- }).from(schema_exports.aiTodos).where(eq(schema_exports.aiTodos.aiSessionId, session.id)).orderBy(asc(schema_exports.aiTodos.createdAt));
113364
- contextData.todos = todos3;
113365
- }
113366
- if (includeFollowUps) {
113367
- const followUps = await db.select({
113368
- followUpReason: schema_exports.manualFollowUps.followUpReason,
113369
- outcome: schema_exports.manualFollowUps.outcome,
113370
- timeSpentMinutes: schema_exports.manualFollowUps.timeSpentMinutes,
113371
- createdAt: schema_exports.manualFollowUps.createdAt
113372
- }).from(schema_exports.manualFollowUps).where(eq(schema_exports.manualFollowUps.aiSessionId, session.id)).orderBy(asc(schema_exports.manualFollowUps.createdAt));
113373
- contextData.followUps = followUps;
113374
- }
113375
- const todosLen = contextData.todos ?? [];
113376
- const completedTodos = todosLen.filter(
113377
- (t8) => t8.status === "completed"
113378
- ).length;
113379
- const followUpsLen = contextData.followUps?.length ?? 0;
113380
- return {
113381
- content: [
113382
- {
113383
- type: "text",
113384
- text: `\u{1F4CB} **Completion Context Retrieved!**
113385
-
113386
- \u{1F3AB} **Ticket:** ${ticket.ticketNumber} - ${ticket.title}
113387
- \u{1F194} **Session:** ${aiSessionId} (${session.status})
113388
- \u23F1\uFE0F **Time:** ${session.actualTimeMinutes || "N/A"}/${session.aiTimeEstimateMinutes || "N/A"} minutes
113389
- \u{1F4CB} **Todos:** ${completedTodos}/${todosLen.length} completed
113390
- \u{1F504} **Follow-ups:** ${followUpsLen}
113391
-
113392
- \u2705 **Full context ready for Cursor AI to generate customer response!**
113393
-
113394
- **Context Data:**
113395
- \`\`\`json
113396
- ${JSON.stringify(contextData, null, 2)}\`\`\``
113397
- }
113398
- ]
113399
- };
113400
- }
113401
- async function handleSaveCustomerResponse(input) {
113402
- const { aiSessionId, customerResponse, responseType = "completion" } = input;
113403
- const scope = await resolveTeamScope(input.teamId);
113404
- if (!scope.ok) return scope.response;
113405
- const prefix = aiSessionId.replace("ai-sess-", "");
113406
- const fullSessionId = await resolveAiSessionId(prefix, scope.teamIds);
113407
- if (!fullSessionId) throw new Error(`Session not found: ${aiSessionId}`);
113408
- await db.insert(schema_exports.aiResponses).values({
113409
- aiSessionId: fullSessionId,
113410
- responseType,
113411
- content: customerResponse,
113412
- isReadyForCustomer: true,
113413
- providerApproved: false
113414
- });
113415
- return {
113416
- content: [
113417
- {
113418
- type: "text",
113419
- text: `\u{1F4BE} **Customer Response Saved!**
113420
-
113421
- \u{1F194} Session: ${aiSessionId}
113422
- \u{1F4DD} Response Type: ${responseType}
113423
- \u{1F4C4} Length: ${customerResponse.length} characters
113424
-
113425
- \u2705 **Response ready for provider approval**
113426
- \u{1F50D} Provider can review in AI tab before sending to customer
113427
-
113428
- **Preview:**
113429
- \`\`\`
113430
- ${customerResponse.substring(0, 200)}${customerResponse.length > 200 ? "..." : ""}\`\`\``
113431
- }
113432
- ]
113433
- };
113434
- }
113435
- async function handleCompleteAiSession(input) {
113436
- const ctx = getAuthContext();
113437
- const {
113438
- aiSessionId,
113439
- workCompleted,
113440
- technicalSummary,
113441
- invoiceDescription,
113442
- efficiencyNotes
113443
- } = input;
113444
- const scope = await resolveTeamScope(input.teamId);
113445
- if (!scope.ok) return scope.response;
113446
- const prefix = aiSessionId.replace("ai-sess-", "");
113447
- const fullSessionId = await resolveAiSessionId(prefix, scope.teamIds);
113448
- if (!fullSessionId) throw new Error(`Session not found: ${aiSessionId}`);
113449
- const [existingSession] = await db.select({
113450
- id: schema_exports.aiSessions.id,
113451
- ticketId: schema_exports.aiSessions.ticketId,
113452
- aiTimeEstimateMinutes: schema_exports.aiSessions.aiTimeEstimateMinutes,
113453
- createdAt: schema_exports.aiSessions.createdAt,
113454
- teamId: schema_exports.aiSessions.teamId
113455
- }).from(schema_exports.aiSessions).where(eq(schema_exports.aiSessions.id, fullSessionId)).limit(1);
113456
- if (!existingSession) {
113457
- throw new Error(`Session not found: ${aiSessionId}`);
113458
- }
113459
- const completionTime = /* @__PURE__ */ new Date();
113460
- const sessionStartTime = new Date(existingSession.createdAt);
113461
- const timeSpentMinutes = Math.round(
113462
- (completionTime.getTime() - sessionStartTime.getTime()) / 6e4
113463
- );
113464
- const [session] = await db.update(schema_exports.aiSessions).set({
113465
- status: "completed",
113466
- actualTimeMinutes: timeSpentMinutes,
113467
- completedAt: completionTime.toISOString(),
113468
- efficiencyScore: null
113469
- }).where(eq(schema_exports.aiSessions.id, existingSession.id)).returning({
113470
- id: schema_exports.aiSessions.id,
113471
- ticketId: schema_exports.aiSessions.ticketId,
113472
- aiTimeEstimateMinutes: schema_exports.aiSessions.aiTimeEstimateMinutes,
113473
- createdAt: schema_exports.aiSessions.createdAt
113474
- });
113475
- if (!session) throw new Error(`Failed to update session: ${aiSessionId}`);
113476
- const efficiencyScore = session.aiTimeEstimateMinutes ? timeSpentMinutes / session.aiTimeEstimateMinutes : 1;
113477
- await db.update(schema_exports.aiSessions).set({ efficiencyScore: efficiencyScore.toFixed(2) }).where(eq(schema_exports.aiSessions.id, session.id));
113478
- const activePhases = await db.select().from(schema_exports.aiTimeLogs).where(
113479
- and(
113480
- eq(schema_exports.aiTimeLogs.aiSessionId, existingSession.id),
113481
- eq(schema_exports.aiTimeLogs.status, "in_progress")
113482
- )
113483
- );
113484
- for (const phase of activePhases) {
113485
- const duration5 = Math.round(
113486
- (completionTime.getTime() - new Date(phase.startedAt).getTime()) / 1e3
113487
- );
113488
- await db.update(schema_exports.aiTimeLogs).set({
113489
- endedAt: completionTime.toISOString(),
113490
- durationSeconds: duration5,
113491
- status: "completed"
113492
- }).where(eq(schema_exports.aiTimeLogs.id, phase.id));
113493
- }
113494
- await db.update(schema_exports.aiTimeLogs).set({ status: "skipped" }).where(
113495
- and(
113496
- eq(schema_exports.aiTimeLogs.aiSessionId, existingSession.id),
113497
- eq(schema_exports.aiTimeLogs.status, "pending"),
113498
- eq(schema_exports.aiTimeLogs.estimatedDurationSeconds, 0)
113499
- )
113500
- );
113501
- const sessionDuration = Math.round(
113502
- (completionTime.getTime() - new Date(session.createdAt).getTime()) / 6e4
113503
- );
113504
- const workSummary = `Completed ${workCompleted.length} tasks including: ${workCompleted.slice(0, 3).join(", ")}${workCompleted.length > 3 ? " and more" : ""}.`;
113505
- const [ticketInfo] = await db.select({
113506
- ticketNumber: schema_exports.tickets.ticketNumber,
113507
- title: schema_exports.tickets.title,
113508
- projectId: schema_exports.tickets.projectId
113509
- }).from(schema_exports.tickets).where(eq(schema_exports.tickets.id, session.ticketId)).limit(1);
113510
- let completionDescription;
113511
- if (invoiceDescription) {
113512
- completionDescription = `${ticketInfo?.ticketNumber || "Ticket"}: ${invoiceDescription}`;
113513
- } else {
113514
- const workDescription = workCompleted.map((task, index2) => `${index2 + 1}. ${task}`).join("\n");
113515
- completionDescription = `${ticketInfo?.ticketNumber || "Ticket"}: ${technicalSummary || workSummary}
113516
-
113517
- Completed work:
113518
- ${workDescription}`;
113519
- }
113520
- const estimatedMinutes = session.aiTimeEstimateMinutes ?? timeSpentMinutes;
113521
- const sessionStart = new Date(session.createdAt);
113522
- const estimatedEnd = new Date(
113523
- sessionStart.getTime() + estimatedMinutes * 6e4
113524
- );
113525
- const existingAgendaEntries = await db.select({
113526
- id: schema_exports.timesheetEvents.id,
113527
- trackedDuration: schema_exports.timesheetEvents.trackedDuration
113528
- }).from(schema_exports.timesheetEvents).where(
113529
- and(
113530
- eq(schema_exports.timesheetEvents.aiSessionId, session.id),
113531
- eq(schema_exports.timesheetEvents.status, "draft")
113532
- )
113533
- ).orderBy(desc(schema_exports.timesheetEvents.createdAt));
113534
- let timesheetEventId = null;
113535
- let wasUpdated = false;
113536
- let consolidatedCount = 0;
113537
- const existingAgendaEntry = existingAgendaEntries[0] ?? null;
113538
- if (existingAgendaEntries.length > 1) {
113539
- const duplicateIds = existingAgendaEntries.slice(1).map((e6) => e6.id);
113540
- await db.delete(schema_exports.timesheetEvents).where(inArray(schema_exports.timesheetEvents.id, duplicateIds));
113541
- consolidatedCount = existingAgendaEntries.length - 1;
113542
- }
113543
- try {
113544
- if (existingAgendaEntry) {
113545
- const [updated] = await db.update(schema_exports.timesheetEvents).set({
113546
- title: ticketInfo?.title || "Development Work",
113547
- description: completionDescription,
113548
- endTime: estimatedEnd.toISOString(),
113549
- projectId: ticketInfo?.projectId ?? null,
113550
- trackedDuration: estimatedMinutes * 60
113551
- }).where(eq(schema_exports.timesheetEvents.id, existingAgendaEntry.id)).returning({ id: schema_exports.timesheetEvents.id });
113552
- timesheetEventId = updated?.id ?? null;
113553
- wasUpdated = true;
113554
- } else {
113555
- const [created] = await db.insert(schema_exports.timesheetEvents).values({
113556
- teamId: existingSession.teamId,
113557
- userId: ctx.userId,
113558
- title: ticketInfo?.title || "Development Work",
113559
- description: completionDescription,
113560
- startTime: sessionStart.toISOString(),
113561
- endTime: estimatedEnd.toISOString(),
113562
- projectId: ticketInfo?.projectId ?? null,
113563
- aiSessionId: session.id,
113564
- type: "work",
113565
- status: "draft",
113566
- allDay: false,
113567
- isTracked: true,
113568
- trackedDuration: estimatedMinutes * 60
113569
- }).returning({ id: schema_exports.timesheetEvents.id });
113570
- timesheetEventId = created?.id ?? null;
113571
- }
113572
- if (timesheetEventId && session.ticketId) {
113573
- await db.insert(schema_exports.timesheetEventTickets).values({
113574
- timesheetEventId,
113575
- ticketId: session.ticketId
113576
- }).onConflictDoNothing();
113577
- }
113578
- } catch (err) {
113579
- console.error(
113580
- `\u26A0\uFE0F Failed to ${wasUpdated ? "update" : "create"} agenda event:`,
113581
- err
113582
- );
113583
- }
113584
- if (consolidatedCount > 0) {
113585
- console.log(
113586
- `\u{1F9F9} Cleaned up ${consolidatedCount} duplicate agenda entries for session ${aiSessionId}`
113587
- );
113588
- }
113589
- let responseText = `\u{1F389} **AI Session Completed Successfully!**
113590
-
113591
- `;
113592
- responseText += `\u{1F194} Session: ${aiSessionId}
113593
- `;
113594
- responseText += `\u{1F4CA} **Performance Summary:**
113595
- `;
113596
- responseText += ` \u2022 Tasks Completed: ${workCompleted.length}
113597
- `;
113598
- responseText += ` \u2022 Time Spent: ${timeSpentMinutes} minutes
113599
- `;
113600
- responseText += ` \u2022 Estimated Time: ${session.aiTimeEstimateMinutes || "N/A"} minutes
113601
- `;
113602
- responseText += ` \u2022 Efficiency: ${efficiencyScore < 1 ? "\u{1F680}" : efficiencyScore > 1.5 ? "\u26A0\uFE0F" : "\u23F1\uFE0F"} ${(efficiencyScore * 100).toFixed(0)}%
113603
- `;
113604
- responseText += ` \u2022 Session Duration: ${sessionDuration} minutes
113605
-
113606
- `;
113607
- responseText += `\u2705 **Work Completed:**
113608
- `;
113609
- workCompleted.forEach((task, index2) => {
113610
- responseText += `${index2 + 1}. ${task}
113611
- `;
113612
- });
113613
- responseText += `
113614
- `;
113615
- if (technicalSummary) {
113616
- responseText += `\u{1F527} **Technical Summary:**
113617
- ${technicalSummary}
113618
-
113619
- `;
113620
- }
113621
- if (efficiencyNotes) {
113622
- responseText += `\u{1F4C8} **Efficiency Notes:**
113623
- ${efficiencyNotes}
113624
-
113625
- `;
113626
- }
113627
- if (timesheetEventId) {
113628
- responseText += `\u{1F4C5} **Timetrack Entry ${wasUpdated ? "Updated" : "Created"}:**
113629
- `;
113630
- responseText += ` \u2022 Agenda event ${wasUpdated ? "updated with final" : "created with"} work summary
113631
- `;
113632
- responseText += ` \u2022 Status: DRAFT (requires approval in agenda)
113633
- `;
113634
- responseText += ` \u2022 Duration: ${estimatedMinutes} minutes
113635
- `;
113636
- responseText += ` \u2022 Period: ${sessionStart.toLocaleString()} - ${completionTime.toLocaleString()}
113637
-
113638
- `;
113639
- }
113640
- responseText += `\u{1F4CB} **Context for Customer Response:**
113641
- `;
113642
- responseText += ` \u2022 Use "get-completion-context" to retrieve full context
113643
- `;
113644
- responseText += ` \u2022 Generate customer-friendly response based on completed work
113645
- `;
113646
- responseText += ` \u2022 Focus on business value and customer benefits
113647
-
113648
- `;
113649
- responseText += `\u{1F3AF} **Session archived successfully!**`;
113650
- return { content: [{ type: "text", text: responseText }] };
113651
- }
113652
-
113653
- // src/tools/sessions.ts
113654
- async function transitionToNextPhase(sessionId, currentPhase) {
113655
- try {
113656
- const now2 = /* @__PURE__ */ new Date();
113657
- const phaseOrder = [
113658
- "analysis",
113659
- "bug_investigation",
113660
- "development",
113661
- "communication"
113662
- ];
113663
- const allPhases = await db.select().from(schema_exports.aiTimeLogs).where(eq(schema_exports.aiTimeLogs.aiSessionId, sessionId)).orderBy(asc(schema_exports.aiTimeLogs.activityType));
113664
- let currentPhaseType = currentPhase;
113665
- if (!currentPhaseType) {
113666
- const activePhase = allPhases.find((p3) => p3.status === "in_progress");
113667
- currentPhaseType = activePhase?.activityType ?? void 0;
113668
- }
113669
- if (!currentPhaseType) {
113670
- const analysisPhase = allPhases.find(
113671
- (p3) => p3.activityType === "analysis"
113672
- );
113673
- if (analysisPhase && analysisPhase.status === "pending" && (analysisPhase.estimatedDurationSeconds ?? 0) > 0) {
113674
- await db.update(schema_exports.aiTimeLogs).set({ status: "in_progress", startedAt: now2.toISOString() }).where(eq(schema_exports.aiTimeLogs.id, analysisPhase.id));
113675
- console.error("\u2705 Started analysis phase");
113676
- }
113677
- return;
113678
- }
113679
- const currentPhaseRecord = allPhases.find(
113680
- (p3) => p3.activityType === currentPhaseType && p3.status === "in_progress"
113681
- );
113682
- if (currentPhaseRecord) {
113683
- const duration5 = Math.round(
113684
- (now2.getTime() - new Date(currentPhaseRecord.startedAt).getTime()) / 1e3
113685
- );
113686
- await db.update(schema_exports.aiTimeLogs).set({
113687
- status: "completed",
113688
- endedAt: now2.toISOString(),
113689
- durationSeconds: duration5
113690
- }).where(eq(schema_exports.aiTimeLogs.id, currentPhaseRecord.id));
113691
- console.error(`\u2705 Completed phase: ${currentPhaseType} (${duration5}s)`);
113692
- }
113693
- const currentIndex = phaseOrder.indexOf(currentPhaseType);
113694
- if (currentIndex === -1 || currentIndex === phaseOrder.length - 1) {
113695
- console.error("No next phase to transition to");
113696
- return;
113697
- }
113698
- for (let i6 = currentIndex + 1; i6 < phaseOrder.length; i6++) {
113699
- const nextPhaseType = phaseOrder[i6];
113700
- const nextPhase = allPhases.find((p3) => p3.activityType === nextPhaseType);
113701
- if (!nextPhase) continue;
113702
- if ((nextPhase.estimatedDurationSeconds ?? 0) === 0) {
113703
- await db.update(schema_exports.aiTimeLogs).set({ status: "skipped" }).where(eq(schema_exports.aiTimeLogs.id, nextPhase.id));
113704
- console.error(
113705
- `\u23ED\uFE0F Skipped phase: ${nextPhaseType} (0 minutes estimated)`
113706
- );
113707
- continue;
113708
- }
113709
- if (nextPhase.status === "pending") {
113710
- await db.update(schema_exports.aiTimeLogs).set({ status: "in_progress", startedAt: now2.toISOString() }).where(eq(schema_exports.aiTimeLogs.id, nextPhase.id));
113711
- console.error(`\u2705 Started next phase: ${nextPhaseType}`);
113712
- return;
113713
- }
113714
- }
113715
- console.error("All remaining phases skipped or completed");
113716
- } catch (error49) {
113717
- console.error("Error transitioning to next phase:", error49);
113718
- }
113719
- }
113720
- async function handleStartAiSession(input) {
113721
- const ctx = getAuthContext();
113722
- const { ticketId, cursorSessionId, totalEstimatedMinutes, complexityScore } = input;
113723
- if (!totalEstimatedMinutes) {
113724
- throw new Error("totalEstimatedMinutes is required");
113725
- }
113726
- const scope = await resolveTeamScope(input.teamId);
113727
- if (!scope.ok) return scope.response;
113728
- const [ticketRow] = await db.select({
113729
- teamId: schema_exports.tickets.teamId,
113730
- projectId: schema_exports.tickets.projectId,
113731
- customerId: schema_exports.tickets.customerId
113732
- }).from(schema_exports.tickets).where(eq(schema_exports.tickets.id, ticketId)).limit(1);
113733
- if (!ticketRow) {
113734
- throw new Error(
113735
- `Ticket not found: ${ticketId}. Call get-tickets to find the correct ticket.`
113736
- );
113737
- }
113738
- const hasTicketAccess = scope.teamIds.includes(ticketRow.teamId) || !!ticketRow.projectId && scope.projectIds.includes(ticketRow.projectId) || !!ticketRow.customerId && scope.customerIds.includes(ticketRow.customerId);
113739
- if (!hasTicketAccess) {
113740
- throw new Error(`No access to ticket: ${ticketId}.`);
113741
- }
113742
- const insertTeamId = ticketRow.teamId;
113743
- const roundedMinutes = roundToNearest15Minutes(totalEstimatedMinutes);
113744
- const sessionStartTime = /* @__PURE__ */ new Date();
113745
- const [sessionData] = await db.insert(schema_exports.aiSessions).values({
113746
- ticketId,
113747
- providerUserId: ctx.userId,
113748
- teamId: insertTeamId,
113749
- cursorSessionId: cursorSessionId ?? null,
113750
- aiTimeEstimateMinutes: roundedMinutes,
113751
- complexityScore: complexityScore ?? null,
113752
- status: "in_progress"
113753
- }).returning({
113754
- id: schema_exports.aiSessions.id,
113755
- ticketId: schema_exports.aiSessions.ticketId,
113756
- cursorSessionId: schema_exports.aiSessions.cursorSessionId,
113757
- createdAt: schema_exports.aiSessions.createdAt
113758
- });
113759
- if (!sessionData) {
113760
- throw new Error("Failed to create AI session");
113761
- }
113762
- const sessionId = `ai-sess-${sessionData.id.substring(0, 8)}`;
113763
- return {
113764
- content: [
113765
- {
113766
- type: "text",
113767
- text: `\u{1F680} **AI Session Started!**
113768
-
113769
- \u{1F194} Session ID: **${sessionId}**
113770
- \u{1F3AB} Ticket: ${ticketId}
113771
- \u23F1\uFE0F Estimated: ${roundedMinutes} min
113772
- ${complexityScore ? `\u{1F3AF} Complexity: ${complexityScore}/10
113773
- ` : ""}\u{1F4C5} Started: ${sessionStartTime.toLocaleString()}
113774
-
113775
- \u{1F4DD} Timetrack entry will be created when you complete the session.`
113776
- }
113777
- ]
113778
- };
113779
- }
113780
- async function handleTrackManualFollowUp(input) {
113781
- const ctx = getAuthContext();
113782
- const {
113783
- aiSessionId,
113784
- originalPrompt,
113785
- aiResponse,
113786
- developerFollowUp,
113787
- followUpReason,
113788
- outcome = "success",
113789
- estimatedMinutes,
113790
- workDescription
113791
- } = input;
113792
- const scope = await resolveTeamScope(input.teamId);
113793
- if (!scope.ok) return scope.response;
113794
- const prefix = aiSessionId.replace("ai-sess-", "");
113795
- const fullSessionId = await resolveAiSessionId(prefix, scope.teamIds);
113796
- if (!fullSessionId) {
113797
- throw new Error(`Session not found: ${aiSessionId}`);
113798
- }
113799
- const [session] = await db.select({
113800
- id: schema_exports.aiSessions.id,
113801
- status: schema_exports.aiSessions.status,
113802
- createdAt: schema_exports.aiSessions.createdAt,
113803
- aiTimeEstimateMinutes: schema_exports.aiSessions.aiTimeEstimateMinutes,
113804
- teamId: schema_exports.aiSessions.teamId
113805
- }).from(schema_exports.aiSessions).where(eq(schema_exports.aiSessions.id, fullSessionId)).limit(1);
113806
- if (!session) throw new Error(`Session not found: ${aiSessionId}`);
113807
- const followUpTime = /* @__PURE__ */ new Date();
113808
- const oldEstimate = session.aiTimeEstimateMinutes ?? 60;
113809
- const roundedFollowUpMinutes = roundToNearest15Minutes(estimatedMinutes || 0);
113810
- const newEstimate = oldEstimate + roundedFollowUpMinutes;
113811
- await db.update(schema_exports.aiSessions).set({
113812
- status: "in_progress",
113813
- aiTimeEstimateMinutes: newEstimate
113814
- }).where(eq(schema_exports.aiSessions.id, session.id));
113815
- await db.insert(schema_exports.manualFollowUps).values({
113816
- aiSessionId: session.id,
113817
- developerId: ctx.userId,
113818
- teamId: session.teamId,
113819
- originalPrompt,
113820
- aiResponse,
113821
- followUpPrompt: developerFollowUp,
113822
- followUpReason,
113823
- outcome,
113824
- timeSpentMinutes: null,
113825
- resolvedAt: outcome === "success" ? (/* @__PURE__ */ new Date()).toISOString() : null
113826
- });
113827
- await db.insert(schema_exports.aiTimeLogs).values({
113828
- aiSessionId: session.id,
113829
- activityType: "debugging",
113830
- description: `Follow-up: ${followUpReason.replace("_", " ")} - ${outcome}`,
113831
- durationSeconds: 0,
113832
- productivityScore: outcome === "success" ? 9 : outcome === "partial_success" ? 6 : 4,
113833
- startedAt: followUpTime.toISOString()
113834
- });
113835
- const sessionStartTime = new Date(session.createdAt);
113836
- const totalMinutesElapsed = Math.round(
113837
- (followUpTime.getTime() - sessionStartTime.getTime()) / 6e4
113838
- );
113839
- const currentEfficiency = totalMinutesElapsed > 0 ? totalMinutesElapsed / newEstimate : 1;
113840
- await db.update(schema_exports.aiSessions).set({
113841
- efficiencyScore: currentEfficiency.toFixed(2),
113842
- actualTimeMinutes: totalMinutesElapsed
113843
- }).where(eq(schema_exports.aiSessions.id, session.id));
113844
- const existingEntries = await db.select({
113845
- id: schema_exports.timesheetEvents.id,
113846
- trackedDuration: schema_exports.timesheetEvents.trackedDuration,
113847
- title: schema_exports.timesheetEvents.title,
113848
- description: schema_exports.timesheetEvents.description,
113849
- startTime: schema_exports.timesheetEvents.startTime
113850
- }).from(schema_exports.timesheetEvents).where(
113851
- and(
113852
- eq(schema_exports.timesheetEvents.aiSessionId, session.id),
113853
- eq(schema_exports.timesheetEvents.status, "draft")
113854
- )
113855
- ).orderBy(desc(schema_exports.timesheetEvents.createdAt));
113856
- let trackerAction = "";
113857
- let trackerDetails = "";
113858
- let existingEntry = existingEntries[0] ?? null;
113859
- if (existingEntries.length > 1) {
113860
- const totalExistingDuration = existingEntries.reduce(
113861
- (sum, entry) => sum + (entry.trackedDuration ?? 0),
113862
- 0
113863
- );
113864
- const duplicateIds = existingEntries.slice(1).map((e6) => e6.id);
113865
- await db.delete(schema_exports.timesheetEvents).where(inArray(schema_exports.timesheetEvents.id, duplicateIds));
113866
- if (existingEntry && totalExistingDuration > (existingEntry.trackedDuration ?? 0)) {
113867
- await db.update(schema_exports.timesheetEvents).set({ trackedDuration: totalExistingDuration }).where(eq(schema_exports.timesheetEvents.id, existingEntry.id));
113868
- existingEntry = {
113869
- ...existingEntry,
113870
- trackedDuration: totalExistingDuration
113871
- };
113872
- }
113873
- trackerAction = `Consolidated ${existingEntries.length} duplicate entries`;
113874
- }
113875
- if (existingEntry) {
113876
- const newDuration = (existingEntry.trackedDuration ?? 0) + roundedFollowUpMinutes * 60;
113877
- await db.update(schema_exports.timesheetEvents).set({
113878
- trackedDuration: newDuration,
113879
- endTime: followUpTime.toISOString(),
113880
- title: workDescription,
113881
- description: workDescription
113882
- }).where(eq(schema_exports.timesheetEvents.id, existingEntry.id));
113883
- trackerAction = trackerAction || "Updated existing tracker";
113884
- trackerDetails = ` \u2022 Total tracked time: ${Math.round(newDuration / 60)} minutes (+${roundedFollowUpMinutes} min)
113885
- \u2022 Description: ${workDescription}
113886
- `;
113887
- } else {
113888
- const durationSeconds = roundedFollowUpMinutes * 60;
113889
- const startTime = new Date(followUpTime.getTime() - durationSeconds * 1e3);
113890
- await db.insert(schema_exports.timesheetEvents).values({
113891
- teamId: session.teamId,
113892
- userId: ctx.userId,
113893
- aiSessionId: session.id,
113894
- title: workDescription,
113895
- description: workDescription,
113896
- startTime: startTime.toISOString(),
113897
- endTime: followUpTime.toISOString(),
113898
- type: "work",
113899
- status: "draft",
113900
- allDay: false,
113901
- isTracked: true,
113902
- trackedDuration: durationSeconds
113903
- });
113904
- trackerAction = "Created new tracker";
113905
- trackerDetails = ` \u2022 Tracked time: ${roundedFollowUpMinutes} minutes
113906
- \u2022 Description: ${workDescription}
113907
- `;
113908
- }
113909
- return {
113910
- content: [
113911
- {
113912
- type: "text",
113913
- text: `\u{1F504} **Follow-up Tracked & Session Restarted!**
113914
-
113915
- \u{1F194} Session: ${aiSessionId} (back to active)
113916
- \u{1F50D} Reason: ${followUpReason.replace("_", " ")}
113917
- \u2705 Outcome: ${outcome}
113918
-
113919
- \u{1F4CA} **Time Estimate Updated:**
113920
- \u2022 Old estimate: ${oldEstimate} minutes
113921
- \u2022 Follow-up estimate: +${roundedFollowUpMinutes} minutes (rounded to 15min)
113922
- \u2022 New estimate: ${newEstimate} minutes
113923
-
113924
- \u{1F4C8} **Current Progress:**
113925
- \u2022 Total time elapsed: ${totalMinutesElapsed} minutes
113926
- \u2022 Efficiency: ${currentEfficiency < 1 ? "\u{1F680} " : currentEfficiency > 1.5 ? "\u26A0\uFE0F " : "\u23F1\uFE0F "}${(currentEfficiency * 100).toFixed(0)}%
113927
-
113928
- \u23F1\uFE0F **Tracker Entry: ${trackerAction}**
113929
- ` + trackerDetails + `
113930
- \u26A1 **Time tracking resumed** - continue with confidence!`
113931
- }
113932
- ]
113933
- };
113934
- }
113935
- async function handleGetSessionContext(input) {
113936
- const {
113937
- aiSessionId,
113938
- includeTicketData = true,
113939
- includeTodoProgress = true,
113940
- includeFollowUpHistory = false
113941
- } = input;
113942
- const scope = await resolveTeamScope(input.teamId);
113943
- if (!scope.ok) return scope.response;
113944
- const prefix = aiSessionId.replace("ai-sess-", "");
113945
- const fullSessionId = await resolveAiSessionId(prefix, scope.teamIds);
113946
- if (!fullSessionId) throw new Error(`Session not found: ${aiSessionId}`);
113947
- const [session] = await db.select({
113948
- id: schema_exports.aiSessions.id,
113949
- ticketId: schema_exports.aiSessions.ticketId,
113950
- status: schema_exports.aiSessions.status,
113951
- aiTimeEstimateMinutes: schema_exports.aiSessions.aiTimeEstimateMinutes,
113952
- actualTimeMinutes: schema_exports.aiSessions.actualTimeMinutes,
113953
- complexityScore: schema_exports.aiSessions.complexityScore,
113954
- createdAt: schema_exports.aiSessions.createdAt,
113955
- cursorSessionId: schema_exports.aiSessions.cursorSessionId
113956
- }).from(schema_exports.aiSessions).where(eq(schema_exports.aiSessions.id, fullSessionId)).limit(1);
113957
- if (!session) throw new Error(`Session not found: ${aiSessionId}`);
113958
- const context2 = {
113959
- status: session.status,
113960
- timeEstimate: session.aiTimeEstimateMinutes,
113961
- actualTime: session.actualTimeMinutes,
113962
- complexity: session.complexityScore,
113963
- createdAt: session.createdAt
113964
- };
113965
- if (includeTicketData) {
113966
- const [ticket] = await db.select({
113967
- id: schema_exports.tickets.id,
113968
- ticketNumber: schema_exports.tickets.ticketNumber,
113969
- title: schema_exports.tickets.title,
113970
- description: schema_exports.tickets.description,
113971
- status: schema_exports.tickets.status,
113972
- priority: schema_exports.tickets.priority,
113973
- type: schema_exports.tickets.type
113974
- }).from(schema_exports.tickets).where(eq(schema_exports.tickets.id, session.ticketId)).limit(1);
113975
- context2.ticketData = ticket ?? null;
113976
- }
113977
- if (includeTodoProgress) {
113978
- const todos3 = await db.select({
113979
- id: schema_exports.aiTodos.id,
113980
- content: schema_exports.aiTodos.content,
113981
- status: schema_exports.aiTodos.status,
113982
- estimatedMinutes: schema_exports.aiTodos.estimatedMinutes,
113983
- actualMinutes: schema_exports.aiTodos.actualMinutes
113984
- }).from(schema_exports.aiTodos).where(eq(schema_exports.aiTodos.aiSessionId, session.id)).orderBy(asc(schema_exports.aiTodos.sequenceOrder));
113985
- context2.todos = todos3;
113986
- context2.todoProgress = {
113987
- total: todos3.length,
113988
- completed: todos3.filter((t8) => t8.status === "completed").length,
113989
- inProgress: todos3.filter((t8) => t8.status === "in_progress").length
113990
- };
113991
- }
113992
- if (includeFollowUpHistory) {
113993
- const followUps = await db.select({
113994
- followUpReason: schema_exports.manualFollowUps.followUpReason,
113995
- outcome: schema_exports.manualFollowUps.outcome,
113996
- timeSpentMinutes: schema_exports.manualFollowUps.timeSpentMinutes,
113997
- createdAt: schema_exports.manualFollowUps.createdAt
113998
- }).from(schema_exports.manualFollowUps).where(eq(schema_exports.manualFollowUps.aiSessionId, session.id)).orderBy(asc(schema_exports.manualFollowUps.createdAt));
113999
- context2.followUpHistory = followUps;
114000
- }
114001
- const ticketData = context2.ticketData;
114002
- const todoProgress = context2.todoProgress;
114003
- const followUpHistory = context2.followUpHistory;
114004
- return {
114005
- content: [
114006
- {
114007
- type: "text",
114008
- text: `\u{1F3AF} **Session Context Retrieved**
114009
-
114010
- Session: ${aiSessionId}
114011
- Status: ${session.status}
114012
- ${ticketData ? `Ticket: ${ticketData.ticketNumber} - ${ticketData.title}
114013
- ` : ""}${todoProgress ? `Todo Progress: ${todoProgress.completed}/${todoProgress.total} completed
114014
- ` : ""}${followUpHistory ? `Follow-ups: ${followUpHistory.length}
114015
- ` : ""}
114016
- \u{1F4CB} Full context preserved for seamless continuation!`
114017
- }
114018
- ]
114019
- };
114020
- }
114021
- async function handleSyncSessionTodos(input) {
114022
- const { aiSessionId, todos: todos3, replaceAll = true } = input;
114023
- const scope = await resolveTeamScope(input.teamId);
114024
- if (!scope.ok) return scope.response;
114025
- const prefix = aiSessionId.replace("ai-sess-", "");
114026
- const fullSessionId = await resolveAiSessionId(prefix, scope.teamIds);
114027
- if (!fullSessionId) throw new Error(`Session not found: ${aiSessionId}`);
114028
- if (replaceAll) {
114029
- await db.delete(schema_exports.aiTodos).where(eq(schema_exports.aiTodos.aiSessionId, fullSessionId));
114030
- }
114031
- if (todos3 && todos3.length > 0) {
114032
- let startSequence = 0;
114033
- if (!replaceAll) {
114034
- const [maxTodo] = await db.select({ sequenceOrder: schema_exports.aiTodos.sequenceOrder }).from(schema_exports.aiTodos).where(eq(schema_exports.aiTodos.aiSessionId, fullSessionId)).orderBy(desc(schema_exports.aiTodos.sequenceOrder)).limit(1);
114035
- startSequence = (maxTodo?.sequenceOrder ?? 0) + 1;
114036
- }
114037
- await db.insert(schema_exports.aiTodos).values(
114038
- todos3.map((todo, index2) => ({
114039
- aiSessionId: fullSessionId,
114040
- content: todo.content,
114041
- status: todo.status,
114042
- cursorTodoId: todo.todoId ?? null,
114043
- estimatedMinutes: todo.estimatedMinutes ?? null,
114044
- sequenceOrder: startSequence + index2
114045
- }))
114046
- );
114047
- }
114048
- let phaseTransition = null;
114049
- const currentTodos = await db.select({ status: schema_exports.aiTodos.status }).from(schema_exports.aiTodos).where(eq(schema_exports.aiTodos.aiSessionId, fullSessionId));
114050
- if (currentTodos.length > 0) {
114051
- const hasInProgress = currentTodos.some((t8) => t8.status === "in_progress");
114052
- const allCompleted = currentTodos.every((t8) => t8.status === "completed");
114053
- const [currentPhase] = await db.select({
114054
- activityType: schema_exports.aiTimeLogs.activityType,
114055
- status: schema_exports.aiTimeLogs.status
114056
- }).from(schema_exports.aiTimeLogs).where(
114057
- and(
114058
- eq(schema_exports.aiTimeLogs.aiSessionId, fullSessionId),
114059
- eq(schema_exports.aiTimeLogs.status, "in_progress")
114060
- )
114061
- ).limit(1);
114062
- if (hasInProgress && currentPhase?.activityType === "analysis") {
114063
- await transitionToNextPhase(fullSessionId, "analysis");
114064
- phaseTransition = "Analysis completed \u2192 Next phase started (Investigation/Development)";
114065
- }
114066
- if (hasInProgress && currentPhase?.activityType === "bug_investigation") {
114067
- const completedCount = currentTodos.filter(
114068
- (t8) => t8.status === "completed"
114069
- ).length;
114070
- if (completedCount > 0) {
114071
- await transitionToNextPhase(fullSessionId, "bug_investigation");
114072
- phaseTransition = "Investigation completed \u2192 Development phase started";
114073
- }
114074
- }
114075
- if (allCompleted && currentPhase?.activityType === "development") {
114076
- await transitionToNextPhase(fullSessionId, "development");
114077
- phaseTransition = "Development completed \u2192 Communication phase started";
114078
- }
114079
- }
114080
- return {
114081
- content: [
114082
- {
114083
- type: "text",
114084
- text: `\u2705 **Todos ${replaceAll ? "Synced" : "Added"} Successfully!**
114085
-
114086
- Session: ${aiSessionId}
114087
- ${replaceAll ? "Synced" : "Added"} ${todos3?.length || 0} todos
114088
- ${replaceAll ? "" : "\u2795 Added to existing todo list\n"}${phaseTransition ? `\u{1F504} Phase Transition: ${phaseTransition}
114089
- ` : ""}
114090
- \u{1F4DD} Todo list updated and tracked for progress monitoring!`
114091
- }
114092
- ]
114093
- };
114094
- }
114095
- async function handleAddFollowUpTodos(input) {
114096
- const { aiSessionId, newTodos, followUpReason } = input;
114097
- const scope = await resolveTeamScope(input.teamId);
114098
- if (!scope.ok) return scope.response;
114099
- const prefix = aiSessionId.replace("ai-sess-", "");
114100
- const fullSessionId = await resolveAiSessionId(prefix, scope.teamIds);
114101
- if (!fullSessionId) throw new Error(`Session not found: ${aiSessionId}`);
114102
- if (newTodos && newTodos.length > 0) {
114103
- const [maxTodo] = await db.select({ sequenceOrder: schema_exports.aiTodos.sequenceOrder }).from(schema_exports.aiTodos).where(eq(schema_exports.aiTodos.aiSessionId, fullSessionId)).orderBy(desc(schema_exports.aiTodos.sequenceOrder)).limit(1);
114104
- const startSequence = (maxTodo?.sequenceOrder ?? 0) + 1;
114105
- await db.insert(schema_exports.aiTodos).values(
114106
- newTodos.map((todo, index2) => ({
114107
- aiSessionId: fullSessionId,
114108
- content: `[Follow-up] ${todo.content}`,
114109
- status: todo.status ?? "pending",
114110
- estimatedMinutes: todo.estimatedMinutes ?? null,
114111
- sequenceOrder: startSequence + index2
114112
- }))
114113
- );
114114
- }
114115
- return {
114116
- content: [
114117
- {
114118
- type: "text",
114119
- text: `\u2705 **Follow-up Todos Added Successfully!**
114120
-
114121
- Session: ${aiSessionId}
114122
- Added ${newTodos?.length || 0} new todos from follow-up
114123
- ${followUpReason ? `Reason: ${followUpReason}
114124
- ` : ""}
114125
- \u{1F4DD} New tasks identified and added to existing workflow!`
114126
- }
114127
- ]
114128
- };
114129
- }
114130
- async function handleUpdateSessionStatus(input) {
114131
- const { aiSessionId, status, actualTimeMinutes, completionNotes } = input;
114132
- const scope = await resolveTeamScope(input.teamId);
114133
- if (!scope.ok) return scope.response;
114134
- const prefix = aiSessionId.replace("ai-sess-", "");
114135
- const fullSessionId = await resolveAiSessionId(prefix, scope.teamIds);
114136
- if (!fullSessionId) throw new Error(`Session not found: ${aiSessionId}`);
114137
- await db.update(schema_exports.aiSessions).set({
114138
- status,
114139
- actualTimeMinutes: actualTimeMinutes ?? null,
114140
- completedAt: status === "completed" ? (/* @__PURE__ */ new Date()).toISOString() : null
114141
- }).where(eq(schema_exports.aiSessions.id, fullSessionId));
114142
- return {
114143
- content: [
114144
- {
114145
- type: "text",
114146
- text: `\u{1F3AF} **Session Status Updated!**
114147
-
114148
- Session: ${aiSessionId}
114149
- Status: ${status}
114150
- ${actualTimeMinutes ? `Actual Time: ${actualTimeMinutes} minutes
114151
- ` : ""}${status === "completed" ? `\u2705 Session completed successfully!
114152
- ` : ""}${completionNotes ? `Notes: ${completionNotes}
114153
- ` : ""}`
114154
- }
114155
- ]
114156
- };
114157
- }
114158
-
114159
113008
  // src/tools/teams.ts
114160
113009
  async function handleGetTeams() {
114161
113010
  const ctx = getAuthContext();
@@ -120976,7 +119825,7 @@ ${tagErrors.map((e6) => ` \u2022 ${e6}`).join("\n")}
120976
119825
  }
120977
119826
 
120978
119827
  // src/server.ts
120979
- var SERVER_VERSION = "3.3.0";
119828
+ var SERVER_VERSION = "3.4.0";
120980
119829
  function createMcpServer() {
120981
119830
  const server = new Server(
120982
119831
  {
@@ -121100,42 +119949,6 @@ function createMcpServer() {
121100
119949
  return await handleLinkDocumentToInvoice(
121101
119950
  asToolArgs(toolArgs)
121102
119951
  );
121103
- case "start-ai-session-smart":
121104
- return await handleStartAiSession(
121105
- asToolArgs(toolArgs)
121106
- );
121107
- case "track-manual-follow-up":
121108
- return await handleTrackManualFollowUp(
121109
- asToolArgs(toolArgs)
121110
- );
121111
- case "get-session-context":
121112
- return await handleGetSessionContext(
121113
- asToolArgs(toolArgs)
121114
- );
121115
- case "sync-session-todos":
121116
- return await handleSyncSessionTodos(
121117
- asToolArgs(toolArgs)
121118
- );
121119
- case "add-follow-up-todos":
121120
- return await handleAddFollowUpTodos(
121121
- asToolArgs(toolArgs)
121122
- );
121123
- case "update-session-status":
121124
- return await handleUpdateSessionStatus(
121125
- asToolArgs(toolArgs)
121126
- );
121127
- case "get-completion-context":
121128
- return await handleGetCompletionContext(
121129
- asToolArgs(toolArgs)
121130
- );
121131
- case "save-customer-response":
121132
- return await handleSaveCustomerResponse(
121133
- asToolArgs(toolArgs)
121134
- );
121135
- case "complete-ai-session":
121136
- return await handleCompleteAiSession(
121137
- asToolArgs(toolArgs)
121138
- );
121139
119952
  case "log-hours":
121140
119953
  return await handleLogHours(asToolArgs(toolArgs));
121141
119954
  case "get-github-file":