@mgsoftwarebv/mcp-server-bridge 3.3.8 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -13
- package/dist/index.js +244 -1116
- 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));
|
|
@@ -106292,239 +106278,103 @@ var TOOLS = [
|
|
|
106292
106278
|
}
|
|
106293
106279
|
},
|
|
106294
106280
|
{
|
|
106295
|
-
name: "
|
|
106296
|
-
description: "
|
|
106281
|
+
name: "get-products",
|
|
106282
|
+
description: "List catalog products used on invoices AND quotes (the shared `invoice_products` catalog). Each entry includes its ID (UUID), name, unit price, currency, unit, active/archived flag, configurable flag, and usage stats. Editing or archiving a catalog product never changes existing invoices/quotes \u2014 those keep an immutable line-item snapshot; catalog changes only affect documents created afterwards.",
|
|
106297
106283
|
inputSchema: {
|
|
106298
106284
|
type: "object",
|
|
106299
106285
|
properties: {
|
|
106300
106286
|
teamId: teamIdProp,
|
|
106301
|
-
|
|
106302
|
-
ticketUrl: { type: "string", description: "URL to the ticket" },
|
|
106303
|
-
cursorSessionId: {
|
|
106287
|
+
q: {
|
|
106304
106288
|
type: "string",
|
|
106305
|
-
description: "
|
|
106306
|
-
},
|
|
106307
|
-
totalEstimatedMinutes: {
|
|
106308
|
-
type: "number",
|
|
106309
|
-
description: "Total estimated time in minutes (senior dev WITHOUT AI, rounded to 15 min)"
|
|
106289
|
+
description: "Search query for product name/description"
|
|
106310
106290
|
},
|
|
106311
|
-
|
|
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: {
|
|
106291
|
+
status: {
|
|
106333
106292
|
type: "string",
|
|
106334
|
-
enum: [
|
|
106335
|
-
|
|
106336
|
-
|
|
106337
|
-
"needs_clarification",
|
|
106338
|
-
"error_in_code"
|
|
106339
|
-
]
|
|
106293
|
+
enum: ["active", "archived", "all"],
|
|
106294
|
+
default: "active",
|
|
106295
|
+
description: "Filter by catalog status (archived = isActive false)"
|
|
106340
106296
|
},
|
|
106341
|
-
|
|
106297
|
+
currency: {
|
|
106342
106298
|
type: "string",
|
|
106343
|
-
|
|
106344
|
-
default: "success"
|
|
106299
|
+
description: "Filter by currency code (e.g. EUR)"
|
|
106345
106300
|
},
|
|
106346
|
-
|
|
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 }
|
|
106301
|
+
pageSize: { type: "number", default: 20, maximum: 100 }
|
|
106377
106302
|
},
|
|
106378
|
-
required: [
|
|
106303
|
+
required: []
|
|
106379
106304
|
}
|
|
106380
106305
|
},
|
|
106381
106306
|
{
|
|
106382
|
-
name: "
|
|
106383
|
-
description: "
|
|
106307
|
+
name: "get-product-by-id",
|
|
106308
|
+
description: "Get a single catalog product by its ID (UUID), including name, unit price, currency, unit, active/archived flag, configurable flag, and usage stats.",
|
|
106384
106309
|
inputSchema: {
|
|
106385
106310
|
type: "object",
|
|
106386
106311
|
properties: {
|
|
106387
106312
|
teamId: teamIdProp,
|
|
106388
|
-
|
|
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
|
-
}
|
|
106313
|
+
productId: { type: "string", description: "Product ID (UUID)" }
|
|
106413
106314
|
},
|
|
106414
|
-
required: ["
|
|
106315
|
+
required: ["productId"]
|
|
106415
106316
|
}
|
|
106416
106317
|
},
|
|
106417
106318
|
{
|
|
106418
|
-
name: "
|
|
106419
|
-
description: "
|
|
106319
|
+
name: "create-product",
|
|
106320
|
+
description: "Create a catalog product for use on invoices and quotes. Stored in the shared `invoice_products` catalog. This only adds a reusable catalog entry; it does not place the product on any document. Returns the created product with its ID and normalized fields.",
|
|
106420
106321
|
inputSchema: {
|
|
106421
106322
|
type: "object",
|
|
106422
106323
|
properties: {
|
|
106423
106324
|
teamId: teamIdProp,
|
|
106424
|
-
|
|
106425
|
-
|
|
106426
|
-
|
|
106427
|
-
|
|
106428
|
-
|
|
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
|
-
}
|
|
106325
|
+
name: { type: "string", description: "Product name" },
|
|
106326
|
+
description: { type: "string" },
|
|
106327
|
+
price: {
|
|
106328
|
+
type: "number",
|
|
106329
|
+
description: "Unit price (catalog default, excl. quantity)"
|
|
106441
106330
|
},
|
|
106442
|
-
|
|
106331
|
+
currency: { type: "string", description: "Currency code (e.g. EUR)" },
|
|
106332
|
+
unit: {
|
|
106443
106333
|
type: "string",
|
|
106444
|
-
description: "
|
|
106334
|
+
description: "Unit label (e.g. hour, piece, month)"
|
|
106445
106335
|
}
|
|
106446
106336
|
},
|
|
106447
|
-
required: ["
|
|
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"]
|
|
106337
|
+
required: ["name"]
|
|
106481
106338
|
}
|
|
106482
106339
|
},
|
|
106483
106340
|
{
|
|
106484
|
-
name: "
|
|
106485
|
-
description: "
|
|
106341
|
+
name: "update-product",
|
|
106342
|
+
description: "Update a catalog product's editable fields (name, description, price, currency, unit) or reactivate it (isActive: true). Only provided fields change. IMPORTANT: updates apply only to FUTURE invoices/quotes. Existing/sent/accepted/paid documents keep their immutable line-item snapshot and are never mutated. Find the product id via get-products.",
|
|
106486
106343
|
inputSchema: {
|
|
106487
106344
|
type: "object",
|
|
106488
106345
|
properties: {
|
|
106489
106346
|
teamId: teamIdProp,
|
|
106490
|
-
|
|
106491
|
-
|
|
106492
|
-
|
|
106493
|
-
|
|
106347
|
+
productId: { type: "string", description: "Product ID (UUID)" },
|
|
106348
|
+
name: { type: "string" },
|
|
106349
|
+
description: { type: ["string", "null"] },
|
|
106350
|
+
price: {
|
|
106351
|
+
type: ["number", "null"],
|
|
106352
|
+
description: "Unit price (catalog default)"
|
|
106494
106353
|
},
|
|
106495
|
-
|
|
106496
|
-
|
|
106497
|
-
|
|
106498
|
-
|
|
106354
|
+
currency: { type: ["string", "null"] },
|
|
106355
|
+
unit: { type: ["string", "null"] },
|
|
106356
|
+
isActive: {
|
|
106357
|
+
type: "boolean",
|
|
106358
|
+
description: "Set true to reactivate an archived product"
|
|
106499
106359
|
}
|
|
106500
106360
|
},
|
|
106501
|
-
required: ["
|
|
106361
|
+
required: ["productId"]
|
|
106502
106362
|
}
|
|
106503
106363
|
},
|
|
106504
106364
|
{
|
|
106505
|
-
name: "
|
|
106506
|
-
description: "
|
|
106365
|
+
name: "archive-product",
|
|
106366
|
+
description: "Archive (soft-disable) a catalog product so it no longer appears in invoice/quote product pickers. Preferred over deletion: products referenced historically stay safe because invoices/quotes hold their own line-item snapshots. Reactivate later via update-product (isActive: true).",
|
|
106507
106367
|
inputSchema: {
|
|
106508
106368
|
type: "object",
|
|
106509
106369
|
properties: {
|
|
106510
106370
|
teamId: teamIdProp,
|
|
106511
|
-
|
|
106512
|
-
|
|
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: {
|
|
106371
|
+
productId: { type: "string", description: "Product ID (UUID)" },
|
|
106372
|
+
reason: {
|
|
106522
106373
|
type: "string",
|
|
106523
|
-
description: "
|
|
106524
|
-
}
|
|
106525
|
-
efficiencyNotes: { type: "string" }
|
|
106374
|
+
description: "Optional note about why it was archived (not persisted)"
|
|
106375
|
+
}
|
|
106526
106376
|
},
|
|
106527
|
-
required: ["
|
|
106377
|
+
required: ["productId"]
|
|
106528
106378
|
}
|
|
106529
106379
|
},
|
|
106530
106380
|
{
|
|
@@ -106542,10 +106392,6 @@ var TOOLS = [
|
|
|
106542
106392
|
type: "string",
|
|
106543
106393
|
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
106394
|
},
|
|
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
106395
|
workDescription: {
|
|
106550
106396
|
type: "string",
|
|
106551
106397
|
description: "Short description of the work done (for the tracker entry)"
|
|
@@ -112400,7 +112246,6 @@ async function handleLogHours(input) {
|
|
|
112400
112246
|
const {
|
|
112401
112247
|
projectId,
|
|
112402
112248
|
ticketId,
|
|
112403
|
-
aiSessionId,
|
|
112404
112249
|
workDescription,
|
|
112405
112250
|
estimatedHours,
|
|
112406
112251
|
chatContextSummary
|
|
@@ -112409,7 +112254,6 @@ async function handleLogHours(input) {
|
|
|
112409
112254
|
if (!scope.ok) return scope.response;
|
|
112410
112255
|
let project = null;
|
|
112411
112256
|
let ticket = null;
|
|
112412
|
-
let aiSession = null;
|
|
112413
112257
|
if (projectId) {
|
|
112414
112258
|
if (!scope.projectIds.includes(projectId)) {
|
|
112415
112259
|
throw new Error(
|
|
@@ -112451,15 +112295,6 @@ async function handleLogHours(input) {
|
|
|
112451
112295
|
}
|
|
112452
112296
|
ticket = ticketData;
|
|
112453
112297
|
}
|
|
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
112298
|
let insertTeamId = ticket?.teamId ?? project?.teamId ?? null;
|
|
112464
112299
|
if (!insertTeamId) {
|
|
112465
112300
|
const resolved = await resolveTeamId(input.teamId);
|
|
@@ -112471,40 +112306,24 @@ async function handleLogHours(input) {
|
|
|
112471
112306
|
let agendaEntry = null;
|
|
112472
112307
|
let wasUpdated = false;
|
|
112473
112308
|
let consolidatedCount = 0;
|
|
112474
|
-
if (
|
|
112309
|
+
if (ticket?.id) {
|
|
112475
112310
|
let existingEntries = [];
|
|
112476
|
-
|
|
112311
|
+
const linkedEvents = await db.select({
|
|
112312
|
+
timesheetEventId: schema_exports.timesheetEventTickets.timesheetEventId
|
|
112313
|
+
}).from(schema_exports.timesheetEventTickets).where(eq(schema_exports.timesheetEventTickets.ticketId, ticket.id));
|
|
112314
|
+
const eventIds = linkedEvents.map((e6) => e6.timesheetEventId);
|
|
112315
|
+
if (eventIds.length > 0) {
|
|
112477
112316
|
existingEntries = await db.select({
|
|
112478
112317
|
id: schema_exports.timesheetEvents.id,
|
|
112479
112318
|
trackedDuration: schema_exports.timesheetEvents.trackedDuration,
|
|
112480
|
-
projectId: schema_exports.timesheetEvents.projectId
|
|
112481
|
-
aiSessionId: schema_exports.timesheetEvents.aiSessionId
|
|
112319
|
+
projectId: schema_exports.timesheetEvents.projectId
|
|
112482
112320
|
}).from(schema_exports.timesheetEvents).where(
|
|
112483
112321
|
and(
|
|
112322
|
+
inArray(schema_exports.timesheetEvents.id, eventIds),
|
|
112484
112323
|
eq(schema_exports.timesheetEvents.status, "draft"),
|
|
112485
|
-
eq(schema_exports.timesheetEvents.userId, ctx.userId)
|
|
112486
|
-
eq(schema_exports.timesheetEvents.aiSessionId, aiSession.id)
|
|
112324
|
+
eq(schema_exports.timesheetEvents.userId, ctx.userId)
|
|
112487
112325
|
)
|
|
112488
112326
|
).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
112327
|
}
|
|
112509
112328
|
if (existingEntries.length > 0) {
|
|
112510
112329
|
const existingEntry = existingEntries[0];
|
|
@@ -112523,8 +112342,7 @@ async function handleLogHours(input) {
|
|
|
112523
112342
|
}).where(eq(schema_exports.timesheetEvents.id, existingEntry.id)).returning({
|
|
112524
112343
|
id: schema_exports.timesheetEvents.id,
|
|
112525
112344
|
trackedDuration: schema_exports.timesheetEvents.trackedDuration,
|
|
112526
|
-
projectId: schema_exports.timesheetEvents.projectId
|
|
112527
|
-
aiSessionId: schema_exports.timesheetEvents.aiSessionId
|
|
112345
|
+
projectId: schema_exports.timesheetEvents.projectId
|
|
112528
112346
|
});
|
|
112529
112347
|
agendaEntry = updated ?? null;
|
|
112530
112348
|
wasUpdated = true;
|
|
@@ -112536,7 +112354,6 @@ async function handleLogHours(input) {
|
|
|
112536
112354
|
teamId: insertTeamId,
|
|
112537
112355
|
userId: ctx.userId,
|
|
112538
112356
|
projectId: project?.id ?? null,
|
|
112539
|
-
aiSessionId: aiSession?.id ?? null,
|
|
112540
112357
|
title: workDescription,
|
|
112541
112358
|
description: chatContextSummary ?? workDescription,
|
|
112542
112359
|
startTime: startTime.toISOString(),
|
|
@@ -112549,8 +112366,7 @@ async function handleLogHours(input) {
|
|
|
112549
112366
|
}).returning({
|
|
112550
112367
|
id: schema_exports.timesheetEvents.id,
|
|
112551
112368
|
trackedDuration: schema_exports.timesheetEvents.trackedDuration,
|
|
112552
|
-
projectId: schema_exports.timesheetEvents.projectId
|
|
112553
|
-
aiSessionId: schema_exports.timesheetEvents.aiSessionId
|
|
112369
|
+
projectId: schema_exports.timesheetEvents.projectId
|
|
112554
112370
|
});
|
|
112555
112371
|
agendaEntry = created ?? null;
|
|
112556
112372
|
if (agendaEntry && ticket?.id) {
|
|
@@ -112581,9 +112397,6 @@ async function handleLogHours(input) {
|
|
|
112581
112397
|
`;
|
|
112582
112398
|
if (ticket)
|
|
112583
112399
|
responseText += ` \u2022 Ticket: ${ticket.title} (${ticket.status})
|
|
112584
|
-
`;
|
|
112585
|
-
if (aiSession)
|
|
112586
|
-
responseText += ` \u2022 AI Session: ${aiSession.id} (${aiSession.status})
|
|
112587
112400
|
`;
|
|
112588
112401
|
responseText += ` \u2022 Description: ${workDescription}
|
|
112589
112402
|
`;
|
|
@@ -113292,868 +113105,201 @@ async function handleRemoveProjectMember(input) {
|
|
|
113292
113105
|
return textResponse(text3);
|
|
113293
113106
|
}
|
|
113294
113107
|
|
|
113295
|
-
// src/tools/
|
|
113296
|
-
|
|
113297
|
-
|
|
113298
|
-
|
|
113299
|
-
|
|
113300
|
-
|
|
113301
|
-
|
|
113302
|
-
|
|
113303
|
-
|
|
113304
|
-
|
|
113305
|
-
|
|
113306
|
-
|
|
113307
|
-
|
|
113308
|
-
|
|
113309
|
-
|
|
113310
|
-
|
|
113311
|
-
|
|
113312
|
-
|
|
113313
|
-
|
|
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
|
-
};
|
|
113108
|
+
// src/tools/products.ts
|
|
113109
|
+
var PRODUCT_STATUSES = ["active", "archived", "all"];
|
|
113110
|
+
var PRODUCT_COLUMNS = {
|
|
113111
|
+
id: schema_exports.invoiceProducts.id,
|
|
113112
|
+
teamId: schema_exports.invoiceProducts.teamId,
|
|
113113
|
+
name: schema_exports.invoiceProducts.name,
|
|
113114
|
+
description: schema_exports.invoiceProducts.description,
|
|
113115
|
+
price: schema_exports.invoiceProducts.price,
|
|
113116
|
+
currency: schema_exports.invoiceProducts.currency,
|
|
113117
|
+
unit: schema_exports.invoiceProducts.unit,
|
|
113118
|
+
isConfigurable: schema_exports.invoiceProducts.isConfigurable,
|
|
113119
|
+
isActive: schema_exports.invoiceProducts.isActive,
|
|
113120
|
+
usageCount: schema_exports.invoiceProducts.usageCount,
|
|
113121
|
+
lastUsedAt: schema_exports.invoiceProducts.lastUsedAt,
|
|
113122
|
+
createdAt: schema_exports.invoiceProducts.createdAt,
|
|
113123
|
+
updatedAt: schema_exports.invoiceProducts.updatedAt
|
|
113124
|
+
};
|
|
113125
|
+
function textResponse2(text3) {
|
|
113126
|
+
return { content: [{ type: "text", text: text3 }] };
|
|
113400
113127
|
}
|
|
113401
|
-
|
|
113402
|
-
|
|
113403
|
-
|
|
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
|
-
};
|
|
113128
|
+
function formatPrice(p3) {
|
|
113129
|
+
if (p3.price == null) return "(no price)";
|
|
113130
|
+
return `${p3.price}${p3.currency ? ` ${p3.currency}` : ""}${p3.unit ? ` / ${p3.unit}` : ""}`;
|
|
113434
113131
|
}
|
|
113435
|
-
|
|
113436
|
-
const
|
|
113437
|
-
|
|
113438
|
-
|
|
113439
|
-
|
|
113440
|
-
|
|
113441
|
-
|
|
113442
|
-
|
|
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
|
-
|
|
113132
|
+
function formatProduct(p3) {
|
|
113133
|
+
const flags = [p3.isActive ? "active" : "archived"];
|
|
113134
|
+
if (p3.isConfigurable) flags.push("configurable");
|
|
113135
|
+
return `**${p3.name}** (${flags.join(", ")})
|
|
113136
|
+
ID: ${p3.id}
|
|
113137
|
+
Price: ${formatPrice(p3)}
|
|
113138
|
+
${p3.description ? `Description: ${p3.description}
|
|
113139
|
+
` : ""}Used: ${p3.usageCount}x${p3.lastUsedAt ? ` (last ${new Date(p3.lastUsedAt).toLocaleDateString()})` : ""}
|
|
113648
113140
|
`;
|
|
113649
|
-
responseText += `\u{1F3AF} **Session archived successfully!**`;
|
|
113650
|
-
return { content: [{ type: "text", text: responseText }] };
|
|
113651
113141
|
}
|
|
113652
|
-
|
|
113653
|
-
|
|
113654
|
-
|
|
113655
|
-
|
|
113656
|
-
|
|
113657
|
-
|
|
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"
|
|
113142
|
+
async function handleGetProducts(input) {
|
|
113143
|
+
const { q: q3, currency, pageSize = 20 } = input;
|
|
113144
|
+
const status = input.status ?? "active";
|
|
113145
|
+
if (!PRODUCT_STATUSES.includes(status)) {
|
|
113146
|
+
return textResponse2(
|
|
113147
|
+
`Error: invalid status "${status}". Allowed: ${PRODUCT_STATUSES.join(", ")}.`
|
|
113681
113148
|
);
|
|
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
113149
|
}
|
|
113726
113150
|
const scope = await resolveTeamScope(input.teamId);
|
|
113727
113151
|
if (!scope.ok) return scope.response;
|
|
113728
|
-
|
|
113729
|
-
|
|
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
|
-
);
|
|
113152
|
+
if (scope.teamIds.length === 0) {
|
|
113153
|
+
return textResponse2("No accessible teams found.");
|
|
113737
113154
|
}
|
|
113738
|
-
const
|
|
113739
|
-
if (
|
|
113740
|
-
|
|
113155
|
+
const filters = [inArray(schema_exports.invoiceProducts.teamId, scope.teamIds)];
|
|
113156
|
+
if (status === "active") {
|
|
113157
|
+
filters.push(eq(schema_exports.invoiceProducts.isActive, true));
|
|
113158
|
+
} else if (status === "archived") {
|
|
113159
|
+
filters.push(eq(schema_exports.invoiceProducts.isActive, false));
|
|
113741
113160
|
}
|
|
113742
|
-
|
|
113743
|
-
|
|
113744
|
-
|
|
113745
|
-
|
|
113746
|
-
|
|
113747
|
-
|
|
113748
|
-
|
|
113749
|
-
|
|
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");
|
|
113161
|
+
if (currency) filters.push(eq(schema_exports.invoiceProducts.currency, currency));
|
|
113162
|
+
if (q3) {
|
|
113163
|
+
filters.push(
|
|
113164
|
+
or(
|
|
113165
|
+
sql`${schema_exports.invoiceProducts.fts} @@ plainto_tsquery('english', ${q3})`,
|
|
113166
|
+
ilike(schema_exports.invoiceProducts.name, `%${q3}%`)
|
|
113167
|
+
)
|
|
113168
|
+
);
|
|
113761
113169
|
}
|
|
113762
|
-
const
|
|
113763
|
-
|
|
113764
|
-
|
|
113765
|
-
|
|
113766
|
-
|
|
113767
|
-
|
|
113768
|
-
|
|
113769
|
-
|
|
113770
|
-
|
|
113771
|
-
|
|
113772
|
-
|
|
113773
|
-
`
|
|
113170
|
+
const rows = await db.select(PRODUCT_COLUMNS).from(schema_exports.invoiceProducts).where(and(...filters)).orderBy(
|
|
113171
|
+
desc(schema_exports.invoiceProducts.usageCount),
|
|
113172
|
+
desc(schema_exports.invoiceProducts.lastUsedAt),
|
|
113173
|
+
asc(schema_exports.invoiceProducts.name)
|
|
113174
|
+
).limit(Math.min(pageSize, 100));
|
|
113175
|
+
if (rows.length === 0) {
|
|
113176
|
+
return textResponse2(
|
|
113177
|
+
`No products found${status !== "all" ? ` (status: ${status})` : ""}.`
|
|
113178
|
+
);
|
|
113179
|
+
}
|
|
113180
|
+
return textResponse2(
|
|
113181
|
+
`Found ${rows.length} product(s):
|
|
113774
113182
|
|
|
113775
|
-
\
|
|
113776
|
-
|
|
113777
|
-
]
|
|
113778
|
-
};
|
|
113183
|
+
${rows.map(formatProduct).join("\n")}`
|
|
113184
|
+
);
|
|
113779
113185
|
}
|
|
113780
|
-
async function
|
|
113781
|
-
const
|
|
113782
|
-
|
|
113783
|
-
aiSessionId,
|
|
113784
|
-
originalPrompt,
|
|
113785
|
-
aiResponse,
|
|
113786
|
-
developerFollowUp,
|
|
113787
|
-
followUpReason,
|
|
113788
|
-
outcome = "success",
|
|
113789
|
-
estimatedMinutes,
|
|
113790
|
-
workDescription
|
|
113791
|
-
} = input;
|
|
113186
|
+
async function handleGetProductById(input) {
|
|
113187
|
+
const { productId } = input;
|
|
113188
|
+
if (!productId) return textResponse2("Error: `productId` is required.");
|
|
113792
113189
|
const scope = await resolveTeamScope(input.teamId);
|
|
113793
113190
|
if (!scope.ok) return scope.response;
|
|
113794
|
-
|
|
113795
|
-
|
|
113796
|
-
|
|
113797
|
-
|
|
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(
|
|
113191
|
+
if (scope.teamIds.length === 0) {
|
|
113192
|
+
return textResponse2("No accessible teams found.");
|
|
113193
|
+
}
|
|
113194
|
+
const [row] = await db.select(PRODUCT_COLUMNS).from(schema_exports.invoiceProducts).where(
|
|
113851
113195
|
and(
|
|
113852
|
-
eq(schema_exports.
|
|
113853
|
-
|
|
113196
|
+
eq(schema_exports.invoiceProducts.id, productId),
|
|
113197
|
+
inArray(schema_exports.invoiceProducts.teamId, scope.teamIds)
|
|
113854
113198
|
)
|
|
113855
|
-
).
|
|
113856
|
-
|
|
113857
|
-
|
|
113858
|
-
|
|
113859
|
-
if (existingEntries.length > 1) {
|
|
113860
|
-
const totalExistingDuration = existingEntries.reduce(
|
|
113861
|
-
(sum, entry) => sum + (entry.trackedDuration ?? 0),
|
|
113862
|
-
0
|
|
113199
|
+
).limit(1);
|
|
113200
|
+
if (!row) {
|
|
113201
|
+
return textResponse2(
|
|
113202
|
+
`Product ${productId} not found or you don't have access to it.`
|
|
113863
113203
|
);
|
|
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
113204
|
}
|
|
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
|
-
};
|
|
113205
|
+
return textResponse2(formatProduct(row));
|
|
113934
113206
|
}
|
|
113935
|
-
async function
|
|
113936
|
-
const
|
|
113937
|
-
|
|
113938
|
-
|
|
113939
|
-
|
|
113940
|
-
|
|
113941
|
-
|
|
113942
|
-
|
|
113943
|
-
|
|
113944
|
-
|
|
113945
|
-
|
|
113946
|
-
|
|
113947
|
-
|
|
113948
|
-
|
|
113949
|
-
|
|
113950
|
-
|
|
113951
|
-
|
|
113952
|
-
|
|
113953
|
-
|
|
113954
|
-
|
|
113955
|
-
|
|
113956
|
-
|
|
113957
|
-
|
|
113958
|
-
|
|
113959
|
-
|
|
113960
|
-
|
|
113961
|
-
|
|
113962
|
-
|
|
113963
|
-
|
|
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**
|
|
113207
|
+
async function loadProductInTeam(productId, teamId) {
|
|
113208
|
+
const accessibleTeamIds = await getAccessibleTeamIds(teamId);
|
|
113209
|
+
const [row] = await db.select(PRODUCT_COLUMNS).from(schema_exports.invoiceProducts).where(
|
|
113210
|
+
and(
|
|
113211
|
+
eq(schema_exports.invoiceProducts.id, productId),
|
|
113212
|
+
inArray(schema_exports.invoiceProducts.teamId, accessibleTeamIds)
|
|
113213
|
+
)
|
|
113214
|
+
).limit(1);
|
|
113215
|
+
return row ?? null;
|
|
113216
|
+
}
|
|
113217
|
+
async function handleCreateProduct(input) {
|
|
113218
|
+
const { name: name21, description, price, currency, unit } = input;
|
|
113219
|
+
if (!name21 || name21.trim().length === 0) {
|
|
113220
|
+
return textResponse2("Error: `name` is required.");
|
|
113221
|
+
}
|
|
113222
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
113223
|
+
if (!resolved.ok) return resolved.response;
|
|
113224
|
+
const [created] = await db.insert(schema_exports.invoiceProducts).values({
|
|
113225
|
+
teamId: resolved.teamId,
|
|
113226
|
+
name: name21.trim(),
|
|
113227
|
+
description: description ?? null,
|
|
113228
|
+
price: price ?? null,
|
|
113229
|
+
currency: currency ?? null,
|
|
113230
|
+
unit: unit ?? null,
|
|
113231
|
+
isActive: true,
|
|
113232
|
+
lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
113233
|
+
}).returning(PRODUCT_COLUMNS);
|
|
113234
|
+
if (!created) return textResponse2("Failed to create product.");
|
|
113235
|
+
return textResponse2(
|
|
113236
|
+
`\u2705 **Product created**
|
|
114009
113237
|
|
|
114010
|
-
|
|
114011
|
-
|
|
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
|
-
};
|
|
113238
|
+
${formatProduct(created)}`
|
|
113239
|
+
);
|
|
114020
113240
|
}
|
|
114021
|
-
async function
|
|
114022
|
-
const {
|
|
114023
|
-
|
|
114024
|
-
|
|
114025
|
-
|
|
114026
|
-
const
|
|
114027
|
-
if (!
|
|
114028
|
-
|
|
114029
|
-
|
|
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
|
-
}))
|
|
113241
|
+
async function handleUpdateProduct(input) {
|
|
113242
|
+
const { productId } = input;
|
|
113243
|
+
if (!productId) return textResponse2("Error: `productId` is required.");
|
|
113244
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
113245
|
+
if (!resolved.ok) return resolved.response;
|
|
113246
|
+
const existing = await loadProductInTeam(productId, resolved.teamId);
|
|
113247
|
+
if (!existing) {
|
|
113248
|
+
return textResponse2(
|
|
113249
|
+
`Product ${productId} not found, or it is not owned by this team.`
|
|
114046
113250
|
);
|
|
114047
113251
|
}
|
|
114048
|
-
|
|
114049
|
-
|
|
114050
|
-
|
|
114051
|
-
|
|
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";
|
|
113252
|
+
const updates = {};
|
|
113253
|
+
if (input.name !== void 0) {
|
|
113254
|
+
if (!input.name || input.name.trim().length === 0) {
|
|
113255
|
+
return textResponse2("Error: `name` cannot be empty.");
|
|
114078
113256
|
}
|
|
113257
|
+
updates.name = input.name.trim();
|
|
114079
113258
|
}
|
|
114080
|
-
|
|
114081
|
-
|
|
114082
|
-
|
|
114083
|
-
|
|
114084
|
-
|
|
114085
|
-
|
|
114086
|
-
|
|
114087
|
-
|
|
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
|
-
}))
|
|
113259
|
+
if (input.description !== void 0) updates.description = input.description;
|
|
113260
|
+
if (input.price !== void 0) updates.price = input.price;
|
|
113261
|
+
if (input.currency !== void 0) updates.currency = input.currency;
|
|
113262
|
+
if (input.unit !== void 0) updates.unit = input.unit;
|
|
113263
|
+
if (input.isActive !== void 0) updates.isActive = input.isActive;
|
|
113264
|
+
if (Object.keys(updates).length === 0) {
|
|
113265
|
+
return textResponse2(
|
|
113266
|
+
"No fields to update. Provide at least one of: name, description, price, currency, unit, isActive."
|
|
114113
113267
|
);
|
|
114114
113268
|
}
|
|
114115
|
-
|
|
114116
|
-
|
|
114117
|
-
|
|
114118
|
-
|
|
114119
|
-
|
|
113269
|
+
updates.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
113270
|
+
const [updated] = await db.update(schema_exports.invoiceProducts).set(updates).where(eq(schema_exports.invoiceProducts.id, existing.id)).returning(PRODUCT_COLUMNS);
|
|
113271
|
+
if (!updated) return textResponse2(`Failed to update product ${productId}.`);
|
|
113272
|
+
return textResponse2(
|
|
113273
|
+
`\u2705 **Product updated**
|
|
114120
113274
|
|
|
114121
|
-
|
|
114122
|
-
|
|
114123
|
-
|
|
114124
|
-
` : ""}
|
|
114125
|
-
\u{1F4DD} New tasks identified and added to existing workflow!`
|
|
114126
|
-
}
|
|
114127
|
-
]
|
|
114128
|
-
};
|
|
113275
|
+
${formatProduct(updated)}
|
|
113276
|
+
Note: this only affects future invoices/quotes. Existing documents keep their line-item snapshots.`
|
|
113277
|
+
);
|
|
114129
113278
|
}
|
|
114130
|
-
async function
|
|
114131
|
-
const {
|
|
114132
|
-
|
|
114133
|
-
|
|
114134
|
-
|
|
114135
|
-
const
|
|
114136
|
-
if (!
|
|
114137
|
-
|
|
114138
|
-
|
|
114139
|
-
|
|
114140
|
-
|
|
114141
|
-
|
|
114142
|
-
|
|
114143
|
-
|
|
114144
|
-
|
|
114145
|
-
|
|
114146
|
-
|
|
113279
|
+
async function handleArchiveProduct(input) {
|
|
113280
|
+
const { productId, reason } = input;
|
|
113281
|
+
if (!productId) return textResponse2("Error: `productId` is required.");
|
|
113282
|
+
const resolved = await resolveTeamId(input.teamId);
|
|
113283
|
+
if (!resolved.ok) return resolved.response;
|
|
113284
|
+
const existing = await loadProductInTeam(productId, resolved.teamId);
|
|
113285
|
+
if (!existing) {
|
|
113286
|
+
return textResponse2(
|
|
113287
|
+
`Product ${productId} not found, or it is not owned by this team.`
|
|
113288
|
+
);
|
|
113289
|
+
}
|
|
113290
|
+
if (!existing.isActive) {
|
|
113291
|
+
return textResponse2(
|
|
113292
|
+
`Product "${existing.name}" (${existing.id}) is already archived.`
|
|
113293
|
+
);
|
|
113294
|
+
}
|
|
113295
|
+
const [archived] = await db.update(schema_exports.invoiceProducts).set({ isActive: false, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq(schema_exports.invoiceProducts.id, existing.id)).returning(PRODUCT_COLUMNS);
|
|
113296
|
+
if (!archived) return textResponse2(`Failed to archive product ${productId}.`);
|
|
113297
|
+
return textResponse2(
|
|
113298
|
+
`\u2705 **Product archived** (hidden from new invoices/quotes; existing documents are untouched).
|
|
114147
113299
|
|
|
114148
|
-
|
|
114149
|
-
|
|
114150
|
-
|
|
114151
|
-
` : ""}${status === "completed" ? `\u2705 Session completed successfully!
|
|
114152
|
-
` : ""}${completionNotes ? `Notes: ${completionNotes}
|
|
114153
|
-
` : ""}`
|
|
114154
|
-
}
|
|
114155
|
-
]
|
|
114156
|
-
};
|
|
113300
|
+
${formatProduct(archived)}${reason ? `Reason: ${reason}
|
|
113301
|
+
` : ""}Reactivate it with update-product (isActive: true).`
|
|
113302
|
+
);
|
|
114157
113303
|
}
|
|
114158
113304
|
|
|
114159
113305
|
// src/tools/teams.ts
|
|
@@ -119647,7 +118793,7 @@ var EXT_MIME = {
|
|
|
119647
118793
|
ppt: "application/vnd.ms-powerpoint",
|
|
119648
118794
|
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
119649
118795
|
};
|
|
119650
|
-
function
|
|
118796
|
+
function textResponse3(text3) {
|
|
119651
118797
|
return { content: [{ type: "text", text: text3 }] };
|
|
119652
118798
|
}
|
|
119653
118799
|
function mimeFromName(name21) {
|
|
@@ -119728,12 +118874,12 @@ async function handleUploadTicketAttachment(input) {
|
|
|
119728
118874
|
(v2) => typeof v2 === "string" && v2.trim().length > 0
|
|
119729
118875
|
);
|
|
119730
118876
|
if (sources.length === 0) {
|
|
119731
|
-
return
|
|
118877
|
+
return textResponse3(
|
|
119732
118878
|
"Provide exactly one source: filePath (absolute local path), imageUrl, or base64Data."
|
|
119733
118879
|
);
|
|
119734
118880
|
}
|
|
119735
118881
|
if (sources.length > 1) {
|
|
119736
|
-
return
|
|
118882
|
+
return textResponse3(
|
|
119737
118883
|
"Provide only one source (filePath, imageUrl, or base64Data), not several."
|
|
119738
118884
|
);
|
|
119739
118885
|
}
|
|
@@ -119753,7 +118899,7 @@ async function handleUploadTicketAttachment(input) {
|
|
|
119753
118899
|
} else if (input.imageUrl) {
|
|
119754
118900
|
const res = await fetch(input.imageUrl);
|
|
119755
118901
|
if (!res.ok) {
|
|
119756
|
-
return
|
|
118902
|
+
return textResponse3(
|
|
119757
118903
|
`Could not download from URL: HTTP ${res.status}.`
|
|
119758
118904
|
);
|
|
119759
118905
|
}
|
|
@@ -119781,22 +118927,22 @@ async function handleUploadTicketAttachment(input) {
|
|
|
119781
118927
|
}
|
|
119782
118928
|
}
|
|
119783
118929
|
} catch (error49) {
|
|
119784
|
-
return
|
|
118930
|
+
return textResponse3(
|
|
119785
118931
|
`Failed to read the file: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
119786
118932
|
);
|
|
119787
118933
|
}
|
|
119788
118934
|
if (buffer2.byteLength === 0) {
|
|
119789
|
-
return
|
|
118935
|
+
return textResponse3("The file is empty (0 bytes); nothing to upload.");
|
|
119790
118936
|
}
|
|
119791
118937
|
if (buffer2.byteLength > MAX_FILE_SIZE) {
|
|
119792
|
-
return
|
|
118938
|
+
return textResponse3(
|
|
119793
118939
|
`File too large (${(buffer2.byteLength / 1024 / 1024).toFixed(
|
|
119794
118940
|
1
|
|
119795
118941
|
)} MB). Max: 25 MB.`
|
|
119796
118942
|
);
|
|
119797
118943
|
}
|
|
119798
118944
|
if (!ALLOWED_MIME_TYPES.has(mimeType)) {
|
|
119799
|
-
return
|
|
118945
|
+
return textResponse3(
|
|
119800
118946
|
`Unsupported file type: ${mimeType}. Allowed: JPEG, PNG, GIF, WebP, PDF, DOC(X), XLS(X), PPT(X), TXT, CSV.`
|
|
119801
118947
|
);
|
|
119802
118948
|
}
|
|
@@ -119809,7 +118955,7 @@ async function handleUploadTicketAttachment(input) {
|
|
|
119809
118955
|
options: { contentType: mimeType, upsert: true }
|
|
119810
118956
|
});
|
|
119811
118957
|
} catch (error49) {
|
|
119812
|
-
return
|
|
118958
|
+
return textResponse3(
|
|
119813
118959
|
`Upload failed: ${error49 instanceof Error ? error49.message : String(error49)}`
|
|
119814
118960
|
);
|
|
119815
118961
|
}
|
|
@@ -119832,7 +118978,7 @@ async function handleUploadTicketAttachment(input) {
|
|
|
119832
118978
|
url3 = signed.url;
|
|
119833
118979
|
} catch {
|
|
119834
118980
|
}
|
|
119835
|
-
return
|
|
118981
|
+
return textResponse3(
|
|
119836
118982
|
`\u{1F4CE} **Attached to ${ticket.ticketNumber}**
|
|
119837
118983
|
File: ${fileName}
|
|
119838
118984
|
Type: ${mimeType}
|
|
@@ -120976,7 +120122,7 @@ ${tagErrors.map((e6) => ` \u2022 ${e6}`).join("\n")}
|
|
|
120976
120122
|
}
|
|
120977
120123
|
|
|
120978
120124
|
// src/server.ts
|
|
120979
|
-
var SERVER_VERSION = "3.
|
|
120125
|
+
var SERVER_VERSION = "3.5.0";
|
|
120980
120126
|
function createMcpServer() {
|
|
120981
120127
|
const server = new Server(
|
|
120982
120128
|
{
|
|
@@ -121100,40 +120246,22 @@ function createMcpServer() {
|
|
|
121100
120246
|
return await handleLinkDocumentToInvoice(
|
|
121101
120247
|
asToolArgs(toolArgs)
|
|
121102
120248
|
);
|
|
121103
|
-
case "
|
|
121104
|
-
return await
|
|
121105
|
-
|
|
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(
|
|
120249
|
+
case "get-products":
|
|
120250
|
+
return await handleGetProducts(asToolArgs(toolArgs));
|
|
120251
|
+
case "get-product-by-id":
|
|
120252
|
+
return await handleGetProductById(
|
|
121125
120253
|
asToolArgs(toolArgs)
|
|
121126
120254
|
);
|
|
121127
|
-
case "
|
|
121128
|
-
return await
|
|
120255
|
+
case "create-product":
|
|
120256
|
+
return await handleCreateProduct(
|
|
121129
120257
|
asToolArgs(toolArgs)
|
|
121130
120258
|
);
|
|
121131
|
-
case "
|
|
121132
|
-
return await
|
|
120259
|
+
case "update-product":
|
|
120260
|
+
return await handleUpdateProduct(
|
|
121133
120261
|
asToolArgs(toolArgs)
|
|
121134
120262
|
);
|
|
121135
|
-
case "
|
|
121136
|
-
return await
|
|
120263
|
+
case "archive-product":
|
|
120264
|
+
return await handleArchiveProduct(
|
|
121137
120265
|
asToolArgs(toolArgs)
|
|
121138
120266
|
);
|
|
121139
120267
|
case "log-hours":
|