@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/README.md +2 -13
- package/dist/index.js +12 -1199
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 (
|
|
112209
|
+
if (ticket?.id) {
|
|
112475
112210
|
let existingEntries = [];
|
|
112476
|
-
|
|
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.
|
|
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":
|