@koderlabs/tasks-mcp 0.1.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.
@@ -0,0 +1,2289 @@
1
+ // @koderlabs/tasks-mcp — generated build, do not edit
2
+
3
+ // src/server.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema
8
+ } from "@modelcontextprotocol/sdk/types.js";
9
+
10
+ // src/tools/discover/whoami.ts
11
+ import { z } from "zod";
12
+
13
+ // src/tools/define.ts
14
+ import { zodToJsonSchema as convertZodToJsonSchema } from "zod-to-json-schema";
15
+ function defineTool(definition) {
16
+ return definition;
17
+ }
18
+ function zodToJsonSchema(schema) {
19
+ const out = convertZodToJsonSchema(schema, {
20
+ target: "openApi3",
21
+ $refStrategy: "none"
22
+ });
23
+ delete out.$schema;
24
+ delete out.definitions;
25
+ return out;
26
+ }
27
+
28
+ // src/tools/discover/whoami.ts
29
+ import { SCOPE } from "@koderlabs/tasks-sdk-types";
30
+ var DESCRIPTION = `
31
+ USE WHEN:
32
+ - You need to know which InstantTasks organization or projects are accessible
33
+ with the current token before running any other tool.
34
+ - The user asks "who am I?" or "what can I access?" in the context of
35
+ InstantTasks.
36
+ - You want to confirm token validity before a long agentic workflow.
37
+
38
+ DO NOT USE:
39
+ - To fetch a specific ticket, issue, or bug report \u2014 use get_ticket_details,
40
+ get_issue_details, or get_bug_report for those.
41
+ - More than once per session unless the token changes.
42
+
43
+ TRIGGER PATTERNS:
44
+ - "what projects do I have access to"
45
+ - "show my organizations"
46
+ - "check my token"
47
+ - "am I logged in to InstantTasks"
48
+ - Before calling find_organizations or find_projects as a pre-flight check.
49
+
50
+ <examples>
51
+ <example>
52
+ <user>What projects do I have access to?</user>
53
+ <tool>whoami</tool>
54
+ <result>You have access to 1 organization: KoderLabs (slug: koderlabs). Projects: FE (Frontend), BE (Backend).</result>
55
+ </example>
56
+ </examples>
57
+ `.trim();
58
+ var whoamiTool = defineTool({
59
+ name: "whoami",
60
+ title: "Identify Current User",
61
+ annotations: { readOnlyHint: true, openWorldHint: true },
62
+ skills: ["discover"],
63
+ requiredScopes: [SCOPE.PROJECT_READ],
64
+ description: DESCRIPTION,
65
+ inputSchema: z.object({}),
66
+ async handler(_params, ctx) {
67
+ const orgs = await ctx.apiClient.listOrganizations();
68
+ if (orgs.length === 0) {
69
+ return {
70
+ content: [{ type: "text", text: "No organizations found for this token." }]
71
+ };
72
+ }
73
+ const lines = [];
74
+ for (const org of orgs) {
75
+ const projects = await ctx.apiClient.listProjects(org.id);
76
+ lines.push(`Organization: ${org.name} (${org.slug})${org.isPersonal ? " [personal]" : ""}`);
77
+ if (projects.length === 0) {
78
+ lines.push(" No projects.");
79
+ } else {
80
+ for (const p of projects) {
81
+ lines.push(` Project: ${p.key} \u2014 ${p.name}`);
82
+ }
83
+ }
84
+ }
85
+ return {
86
+ content: [{ type: "text", text: lines.join("\n") }]
87
+ };
88
+ }
89
+ });
90
+
91
+ // src/tools/discover/find-organizations.ts
92
+ import { z as z2 } from "zod";
93
+ import { SCOPE as SCOPE2 } from "@koderlabs/tasks-sdk-types";
94
+
95
+ // src/formatting/ticket.ts
96
+ function formatTicket(t) {
97
+ const lines = [
98
+ `${t.key} \u2014 ${t.title}`,
99
+ `Status: ${t.status} | Priority: ${t.priority}`,
100
+ t.assigneeId ? `Assignee ID: ${t.assigneeId}` : "Assignee: unassigned",
101
+ t.reporterId ? `Reporter ID: ${t.reporterId}` : "",
102
+ `Created: ${t.createdAt.slice(0, 10)} | Updated: ${t.updatedAt.slice(0, 10)}`
103
+ ];
104
+ if (t.description) {
105
+ lines.push("", "Description:", t.description.slice(0, 2e3));
106
+ if (t.description.length > 2e3) lines.push("[description truncated]");
107
+ }
108
+ const cfKeys = Object.keys(t.customFields ?? {}).filter(
109
+ (k) => !["screenshotAttachmentId", "annotationsAttachmentId", "sdkEventId", "sdkMetadata"].includes(k)
110
+ );
111
+ if (cfKeys.length) {
112
+ lines.push("", "Custom fields:");
113
+ for (const k of cfKeys) {
114
+ lines.push(` ${k}: ${JSON.stringify(t.customFields[k])}`);
115
+ }
116
+ }
117
+ return lines.filter(Boolean).join("\n");
118
+ }
119
+ function formatTicketList(tickets) {
120
+ const lines = tickets.map(
121
+ (t) => `${t.key} \u2014 ${t.title} [${t.status}] [${t.priority}]`
122
+ );
123
+ return `${tickets.length} ticket(s) found:
124
+ ${lines.join("\n")}`;
125
+ }
126
+ function formatOrganizationList(orgs) {
127
+ if (orgs.length === 0) return "No organizations found.";
128
+ const lines = orgs.map(
129
+ (o) => `${o.name} (slug: ${o.slug}, personal: ${o.isPersonal})`
130
+ );
131
+ return `${orgs.length} organization(s):
132
+ ${lines.join("\n")}`;
133
+ }
134
+
135
+ // src/tools/discover/find-organizations.ts
136
+ var DESCRIPTION2 = `
137
+ USE WHEN:
138
+ - You need a list of organizations the current token can access, e.g. before
139
+ calling find_projects with a specific organizationId.
140
+ - The user asks which teams, workspaces, or organizations they belong to.
141
+
142
+ DO NOT USE:
143
+ - To get project details \u2014 use find_projects instead.
144
+ - When you already have the organizationId you need.
145
+
146
+ TRIGGER PATTERNS:
147
+ - "list my organizations"
148
+ - "show all workspaces"
149
+ - "what teams / orgs do I have?"
150
+ - "organization slug for <name>"
151
+
152
+ <examples>
153
+ <example>
154
+ <user>List all my organizations.</user>
155
+ <tool>find_organizations</tool>
156
+ <result>1 organization found: KoderLabs (slug: koderlabs, personal: false)</result>
157
+ </example>
158
+ </examples>
159
+ `.trim();
160
+ var findOrganizationsTool = defineTool({
161
+ name: "find_organizations",
162
+ title: "Find Organizations",
163
+ annotations: { readOnlyHint: true, openWorldHint: true },
164
+ skills: ["discover"],
165
+ requiredScopes: [SCOPE2.PROJECT_READ],
166
+ description: DESCRIPTION2,
167
+ inputSchema: z2.object({}),
168
+ async handler(_params, ctx) {
169
+ const orgs = await ctx.apiClient.listOrganizations();
170
+ return {
171
+ content: [{ type: "text", text: formatOrganizationList(orgs) }]
172
+ };
173
+ }
174
+ });
175
+
176
+ // src/tools/discover/find-projects.ts
177
+ import { z as z3 } from "zod";
178
+ import { SCOPE as SCOPE3 } from "@koderlabs/tasks-sdk-types";
179
+
180
+ // src/errors.ts
181
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
182
+ var UserInputError = class extends McpError {
183
+ constructor(message) {
184
+ super(ErrorCode.InvalidParams, message);
185
+ this.name = "UserInputError";
186
+ }
187
+ };
188
+ var ApiNotFoundError = class extends McpError {
189
+ constructor(resource, id) {
190
+ super(ErrorCode.InvalidParams, `${resource} not found: ${id}`);
191
+ this.name = "ApiNotFoundError";
192
+ }
193
+ };
194
+ var ScopeError = class extends McpError {
195
+ constructor(requiredScope) {
196
+ super(ErrorCode.InvalidRequest, `Token missing required scope: ${requiredScope}`);
197
+ this.name = "ScopeError";
198
+ }
199
+ };
200
+ var ApiError = class extends McpError {
201
+ constructor(message, statusCode) {
202
+ super(ErrorCode.InternalError, `InstantTasks API error: ${message}`);
203
+ this.statusCode = statusCode;
204
+ this.name = "ApiError";
205
+ }
206
+ statusCode;
207
+ };
208
+ var AuthError = class extends McpError {
209
+ constructor(reason, detail) {
210
+ super(ErrorCode.InvalidRequest, detail ? `${reason}: ${detail}` : reason);
211
+ this.reason = reason;
212
+ this.name = "AuthError";
213
+ }
214
+ reason;
215
+ };
216
+
217
+ // src/tools/discover/find-projects.ts
218
+ var DESCRIPTION3 = `
219
+ USE WHEN:
220
+ - You need a project's key (e.g. "FE", "BE") to pass into find_tickets,
221
+ get_ticket_details, or find_issues.
222
+ - The user asks to list projects in a workspace or organization.
223
+
224
+ DO NOT USE:
225
+ - To fetch tickets \u2014 use find_tickets or get_ticket_details.
226
+ - Without an organizationId \u2014 call find_organizations first.
227
+
228
+ TRIGGER PATTERNS:
229
+ - "show projects in <org>"
230
+ - "what project key is <name>?"
231
+ - "list all projects"
232
+
233
+ <examples>
234
+ <example>
235
+ <user>What projects are in the KoderLabs organization?</user>
236
+ <tool>find_projects { "organizationId": "uuid-here" }</tool>
237
+ <result>3 projects: FE (Frontend), BE (Backend), MOB (Mobile)</result>
238
+ </example>
239
+ </examples>
240
+ `.trim();
241
+ var findProjectsTool = defineTool({
242
+ name: "find_projects",
243
+ title: "Find Projects",
244
+ annotations: { readOnlyHint: true, openWorldHint: true },
245
+ skills: ["discover"],
246
+ requiredScopes: [SCOPE3.PROJECT_READ],
247
+ description: DESCRIPTION3,
248
+ inputSchema: z3.object({
249
+ organizationId: z3.string().describe("UUID of the organization")
250
+ }),
251
+ async handler(params, ctx) {
252
+ if (!params.organizationId) throw new UserInputError("organizationId is required");
253
+ const projects = await ctx.apiClient.listProjects(params.organizationId);
254
+ if (projects.length === 0) {
255
+ return { content: [{ type: "text", text: "No projects found." }] };
256
+ }
257
+ const lines = projects.map((p) => `${p.key} \u2014 ${p.name}${p.description ? `: ${p.description}` : ""}`);
258
+ return { content: [{ type: "text", text: `${projects.length} project(s):
259
+ ${lines.join("\n")}` }] };
260
+ }
261
+ });
262
+
263
+ // src/tools/discover/find-releases.ts
264
+ import { z as z4 } from "zod";
265
+ import { SCOPE as SCOPE4 } from "@koderlabs/tasks-sdk-types";
266
+ var DESCRIPTION4 = `
267
+ USE WHEN:
268
+ - You need to know which releases or versions exist in a project, e.g. to
269
+ filter bug reports to a specific release or answer "what version shipped X?".
270
+ - The user asks for release history or version list.
271
+
272
+ DO NOT USE:
273
+ - To look up ticket-level version data \u2014 use get_ticket_details instead.
274
+ - When you already have the version string you need.
275
+
276
+ TRIGGER PATTERNS:
277
+ - "list releases for <project>"
278
+ - "what versions exist in <project>?"
279
+ - "show release history"
280
+ - "when was v2.3 released?"
281
+
282
+ <examples>
283
+ <example>
284
+ <user>List recent releases in the FE project.</user>
285
+ <tool>find_releases { "projectId": "uuid" }</tool>
286
+ <result>5 releases: v2.5.0 (2026-05-01), v2.4.3 (2026-04-15), ...</result>
287
+ </example>
288
+ </examples>
289
+ `.trim();
290
+ var findReleasesTool = defineTool({
291
+ name: "find_releases",
292
+ title: "Find Releases",
293
+ annotations: { readOnlyHint: true, openWorldHint: true },
294
+ skills: ["discover"],
295
+ requiredScopes: [SCOPE4.PROJECT_READ],
296
+ description: DESCRIPTION4,
297
+ inputSchema: z4.object({
298
+ projectId: z4.string().describe("UUID of the project"),
299
+ limit: z4.number().optional().describe("Max releases to return (default 20)")
300
+ }),
301
+ async handler(params, ctx) {
302
+ if (!params.projectId) throw new UserInputError("projectId is required");
303
+ const releases = await ctx.apiClient.listReleases(params.projectId, params.limit);
304
+ if (releases.length === 0) {
305
+ return { content: [{ type: "text", text: "No releases found for this project." }] };
306
+ }
307
+ const lines = releases.map(
308
+ (r) => `${r.version}${r.releasedAt ? ` \u2014 released ${r.releasedAt.slice(0, 10)}` : " (unreleased)"}`
309
+ );
310
+ return { content: [{ type: "text", text: `${releases.length} release(s):
311
+ ${lines.join("\n")}` }] };
312
+ }
313
+ });
314
+
315
+ // src/tools/triage/find-issues.ts
316
+ import { z as z5 } from "zod";
317
+ import { SCOPE as SCOPE5 } from "@koderlabs/tasks-sdk-types";
318
+
319
+ // src/formatting/issue.ts
320
+ function formatIssue(issue) {
321
+ const firstSeen = issue.firstSeen.slice(0, 10);
322
+ const lastSeen = issue.lastSeen.slice(0, 10);
323
+ return [
324
+ `Issue: ${issue.title}`,
325
+ `Culprit: ${issue.culprit}`,
326
+ `Status: ${issue.status} | Level: ${issue.level}`,
327
+ `Occurrences: ${issue.count.toLocaleString()}`,
328
+ `First seen: ${firstSeen} | Last seen: ${lastSeen}`,
329
+ `Project ID: ${issue.projectId}`
330
+ ].join("\n");
331
+ }
332
+ function formatIssueList(issues) {
333
+ const lines = issues.map(
334
+ (i) => `[${i.level.toUpperCase()}] ${i.title} \u2014 ${i.count} occurrences, last seen ${i.lastSeen.slice(0, 10)} (${i.status})`
335
+ );
336
+ return `${issues.length} issue(s) found:
337
+ ${lines.join("\n")}`;
338
+ }
339
+
340
+ // src/tools/triage/find-issues.ts
341
+ var DESCRIPTION5 = `
342
+ USE WHEN:
343
+ - You need to search for SDK-captured error issues in a project (crashes,
344
+ exceptions, unhandled promise rejections captured by the InstantTasks SDK).
345
+ - The user mentions "errors", "crashes", "exceptions", or "issues in prod".
346
+ - You want to triage recent errors before diving into a specific ticket.
347
+
348
+ DO NOT USE:
349
+ - To search project management tickets \u2014 use find_tickets for those.
350
+ - When you already have a specific issue ID \u2014 use get_issue_details instead.
351
+
352
+ TRIGGER PATTERNS:
353
+ - "show recent errors in <project>"
354
+ - "what crashes happened in prod?"
355
+ - "find issues with status unresolved"
356
+ - "search for TypeError in <project>"
357
+
358
+ DEPENDENCY:
359
+ - Requires Phase C (Issues module) to be merged on the API. Returns a
360
+ "module not yet available" notice if the API returns 404.
361
+
362
+ <examples>
363
+ <example>
364
+ <user>Show unresolved errors in the FE project.</user>
365
+ <tool>find_issues { "projectId": "uuid", "filter": "status:unresolved" }</tool>
366
+ <result>12 unresolved issues found: TypeError in App.tsx (last seen 2h ago), ...</result>
367
+ </example>
368
+ </examples>
369
+ `.trim();
370
+ var findIssuesTool = defineTool({
371
+ name: "find_issues",
372
+ title: "Find Issues",
373
+ annotations: { readOnlyHint: true, openWorldHint: true },
374
+ meta: { "anthropic/maxResultSizeChars": 3e5 },
375
+ skills: ["triage"],
376
+ requiredScopes: [SCOPE5.EVENT_READ],
377
+ description: DESCRIPTION5,
378
+ inputSchema: z5.object({
379
+ projectId: z5.string().describe("UUID of the project to search"),
380
+ filter: z5.string().optional().describe('Free-text or structured query, e.g. "status:unresolved TypeError"'),
381
+ limit: z5.number().optional().describe("Max issues to return (default 25)")
382
+ }),
383
+ async handler(params, ctx) {
384
+ if (!params.projectId) throw new UserInputError("projectId is required");
385
+ const issues = await ctx.apiClient.listIssues(params.projectId, params.filter, params.limit);
386
+ if (issues === null) {
387
+ return {
388
+ content: [{
389
+ type: "text",
390
+ text: "The Issues module is not yet available on this InstantTasks instance (Phase C dependency). Use find_tickets to search project management tickets instead."
391
+ }]
392
+ };
393
+ }
394
+ if (issues.length === 0) {
395
+ return { content: [{ type: "text", text: "No issues found matching your filter." }] };
396
+ }
397
+ return { content: [{ type: "text", text: formatIssueList(issues) }] };
398
+ }
399
+ });
400
+
401
+ // src/tools/triage/find-tickets.ts
402
+ import { z as z6 } from "zod";
403
+ import { SCOPE as SCOPE6 } from "@koderlabs/tasks-sdk-types";
404
+ var DESCRIPTION6 = `
405
+ USE WHEN:
406
+ - You need to search InstantTasks project management tickets (tasks, bugs,
407
+ stories, epics) using natural language or IQL.
408
+ - The user references tickets, tasks, bugs in a project management context
409
+ (not SDK-captured errors \u2014 use find_issues for those).
410
+ - You want to list open tickets, tickets assigned to someone, or tickets
411
+ matching a text query.
412
+
413
+ DO NOT USE:
414
+ - To find SDK-captured errors or crashes \u2014 use find_issues for those.
415
+ - When you already have a ticket key (e.g. FE-42) \u2014 use get_ticket_details.
416
+
417
+ TRIGGER PATTERNS:
418
+ - "find tickets assigned to <user>"
419
+ - "show open bugs in <project>"
420
+ - "list in-progress tickets"
421
+ - "search for tickets about <topic>"
422
+ - "what tickets are blocked?"
423
+ - "IQL: <query>"
424
+
425
+ IQL EXAMPLES:
426
+ - project = FE AND status = "In Progress"
427
+ - assignee = currentUser() AND priority in (High, Critical)
428
+ - text ~ "payment" AND created > -7d ORDER BY priority DESC
429
+ - sprint in openSprints() AND status != Done
430
+
431
+ <examples>
432
+ <example>
433
+ <user>Show open high-priority bugs in the FE project.</user>
434
+ <tool>find_tickets { "query": "project = FE AND status != Done AND priority = High", "limit": 10 }</tool>
435
+ <result>8 tickets found: FE-142 - Login page crash (High, In Progress), ...</result>
436
+ </example>
437
+ </examples>
438
+ `.trim();
439
+ var findTicketsTool = defineTool({
440
+ name: "find_tickets",
441
+ title: "Find Tickets",
442
+ annotations: { readOnlyHint: true, openWorldHint: true },
443
+ meta: { "anthropic/maxResultSizeChars": 3e5 },
444
+ skills: ["triage"],
445
+ requiredScopes: [SCOPE6.TICKET_READ],
446
+ description: DESCRIPTION6,
447
+ inputSchema: z6.object({
448
+ query: z6.string().describe("IQL query string or natural language search"),
449
+ projectKey: z6.string().optional().describe('Project key to scope search (e.g. "FE")'),
450
+ limit: z6.number().optional().describe("Max tickets to return (default 25)")
451
+ }),
452
+ async handler(params, ctx) {
453
+ if (!params.query) throw new UserInputError("query is required");
454
+ const tickets = await ctx.apiClient.listTicketsByIQL(params.query, params.projectKey, params.limit);
455
+ if (tickets.length === 0) {
456
+ return { content: [{ type: "text", text: "No tickets found matching your query." }] };
457
+ }
458
+ return { content: [{ type: "text", text: formatTicketList(tickets) }] };
459
+ }
460
+ });
461
+
462
+ // src/tools/triage/get-issue-details.ts
463
+ import { z as z7 } from "zod";
464
+ import { SCOPE as SCOPE7 } from "@koderlabs/tasks-sdk-types";
465
+ var DESCRIPTION7 = `
466
+ USE WHEN:
467
+ - You have an issue ID (from find_issues) and want full details: title,
468
+ culprit, occurrence count, first/last seen, status, level.
469
+ - The user asks to investigate or describe a specific SDK-captured error.
470
+
471
+ DO NOT USE:
472
+ - When you need event-level detail (breadcrumbs, stack trace) \u2014 use
473
+ get_event_details for that.
474
+ - For project management tickets \u2014 use get_ticket_details.
475
+
476
+ TRIGGER PATTERNS:
477
+ - "tell me about issue <id>"
478
+ - "describe error <id>"
479
+ - "show details for this crash"
480
+
481
+ DEPENDENCY: Requires Phase C (Issues module) on the API.
482
+
483
+ <examples>
484
+ <example>
485
+ <user>Show details for issue abc123.</user>
486
+ <tool>get_issue_details { "issueId": "abc123" }</tool>
487
+ <result>TypeError: Cannot read property 'x' of null | culprit: App.tsx | count: 42 | last seen: 2h ago</result>
488
+ </example>
489
+ </examples>
490
+ `.trim();
491
+ var getIssueDetailsTool = defineTool({
492
+ name: "get_issue_details",
493
+ title: "Get Issue Details",
494
+ annotations: { readOnlyHint: true, openWorldHint: true },
495
+ skills: ["triage"],
496
+ requiredScopes: [SCOPE7.EVENT_READ],
497
+ description: DESCRIPTION7,
498
+ inputSchema: z7.object({
499
+ issueId: z7.string().describe("Issue ID from find_issues output")
500
+ }),
501
+ async handler(params, ctx) {
502
+ if (!params.issueId) throw new UserInputError("issueId is required");
503
+ const issue = await ctx.apiClient.getIssue(params.issueId);
504
+ if (!issue) {
505
+ return {
506
+ content: [{
507
+ type: "text",
508
+ text: `Issue ${params.issueId} not found. Either the ID is incorrect or the Issues module (Phase C) is not yet deployed on this instance.`
509
+ }]
510
+ };
511
+ }
512
+ return { content: [{ type: "text", text: formatIssue(issue) }] };
513
+ }
514
+ });
515
+
516
+ // src/tools/triage/get-ticket-details.ts
517
+ import { z as z8 } from "zod";
518
+ import { SCOPE as SCOPE8 } from "@koderlabs/tasks-sdk-types";
519
+ var DESCRIPTION8 = `
520
+ USE WHEN:
521
+ - You have a ticket key (e.g. "FE-42") and need its full details: title,
522
+ description, status, priority, assignee, custom fields, comments count.
523
+ - The user references a specific ticket by key and asks what it contains.
524
+ - You want to read the full description or custom fields of a ticket before
525
+ suggesting a fix.
526
+
527
+ DO NOT USE:
528
+ - To search for tickets \u2014 use find_tickets with an IQL query instead.
529
+ - For SDK-captured error events \u2014 use get_event_details or get_bug_report.
530
+
531
+ TRIGGER PATTERNS:
532
+ - "what is FE-42?"
533
+ - "show ticket <key>"
534
+ - "describe <KEY> ticket"
535
+ - "open ticket <key>"
536
+ - "ticket details for <key>"
537
+
538
+ <examples>
539
+ <example>
540
+ <user>What is ticket FE-142?</user>
541
+ <tool>get_ticket_details { "ticketKey": "FE-142" }</tool>
542
+ <result>FE-142 \u2014 Login page crashes on Safari | Status: In Progress | Priority: High | Assignee: jane@example.com</result>
543
+ </example>
544
+ </examples>
545
+ `.trim();
546
+ var getTicketDetailsTool = defineTool({
547
+ name: "get_ticket_details",
548
+ title: "Get Ticket Details",
549
+ annotations: { readOnlyHint: true, openWorldHint: true },
550
+ meta: { "anthropic/maxResultSizeChars": 2e5 },
551
+ skills: ["triage"],
552
+ requiredScopes: [SCOPE8.TICKET_READ],
553
+ description: DESCRIPTION8,
554
+ inputSchema: z8.object({
555
+ ticketKey: z8.string().describe('Ticket key in PROJECT-NUMBER format, e.g. "FE-42"')
556
+ }),
557
+ async handler(params, ctx) {
558
+ if (!params.ticketKey) throw new UserInputError("ticketKey is required");
559
+ const ticket = await ctx.apiClient.getTicketByKey(params.ticketKey);
560
+ if (!ticket) throw new ApiNotFoundError("Ticket", params.ticketKey);
561
+ return { content: [{ type: "text", text: formatTicket(ticket) }] };
562
+ }
563
+ });
564
+
565
+ // src/tools/triage/get-event-details.ts
566
+ import { z as z9 } from "zod";
567
+ import { SCOPE as SCOPE9 } from "@koderlabs/tasks-sdk-types";
568
+
569
+ // src/formatting/event.ts
570
+ function formatEvent(event) {
571
+ const lines = [
572
+ `Event: ${event.id}`,
573
+ `Message: ${event.message}`,
574
+ `Level: ${event.level} | Timestamp: ${event.timestamp}`
575
+ ];
576
+ if (Object.keys(event.tags ?? {}).length) {
577
+ lines.push("Tags: " + Object.entries(event.tags).map(([k, v]) => `${k}=${v}`).join(", "));
578
+ }
579
+ if (event.request) {
580
+ const req = event.request;
581
+ lines.push(`Request: ${req.method ?? "?"} ${req.url ?? "?"}`);
582
+ }
583
+ if (event.breadcrumbs?.length) {
584
+ lines.push("", `Breadcrumbs (${event.breadcrumbs.length} entries):`);
585
+ lines.push(formatBreadcrumbs(event.breadcrumbs.slice(-10)));
586
+ if (event.breadcrumbs.length > 10) {
587
+ lines.push(`[${event.breadcrumbs.length - 10} earlier entries omitted]`);
588
+ }
589
+ }
590
+ if (Object.keys(event.extra ?? {}).length) {
591
+ lines.push("", "Extra context:", JSON.stringify(event.extra, null, 2).slice(0, 500));
592
+ }
593
+ return lines.join("\n");
594
+ }
595
+ function formatBreadcrumbs(breadcrumbs) {
596
+ return breadcrumbs.map((b) => {
597
+ const ts = b.timestamp ? b.timestamp.slice(11, 19) : "?";
598
+ const data = Object.keys(b.data ?? {}).length ? ` | ${JSON.stringify(b.data)}` : "";
599
+ return `${ts} [${b.category}] ${b.message}${data}`;
600
+ }).join("\n");
601
+ }
602
+ function bufferToBase64(buffer) {
603
+ const bytes = new Uint8Array(buffer);
604
+ let binary = "";
605
+ for (let i = 0; i < bytes.byteLength; i++) {
606
+ binary += String.fromCharCode(bytes[i]);
607
+ }
608
+ return btoa(binary);
609
+ }
610
+
611
+ // src/tools/triage/get-event-details.ts
612
+ var DESCRIPTION9 = `
613
+ USE WHEN:
614
+ - You have an event ID and want the complete event payload: message, level,
615
+ timestamp, breadcrumbs, tags, extra context, request info.
616
+ - The user asks to investigate a specific occurrence of an error.
617
+ - You want breadcrumbs or stack details to understand *what the user did*
618
+ before the crash happened.
619
+
620
+ DO NOT USE:
621
+ - To get issue-level aggregate data \u2014 use get_issue_details for that.
622
+ - For widget bug screenshots \u2014 use get_bug_screenshot.
623
+ - For project tickets \u2014 use get_ticket_details.
624
+
625
+ TRIGGER PATTERNS:
626
+ - "show event <id>"
627
+ - "what happened in this occurrence?"
628
+ - "get breadcrumbs for event <id>"
629
+ - "what was the request when this error happened?"
630
+
631
+ DEPENDENCY: Requires Phase C (Issues + Events module) on the API.
632
+
633
+ <examples>
634
+ <example>
635
+ <user>Show me event details for event xyz789.</user>
636
+ <tool>get_event_details { "eventId": "xyz789" }</tool>
637
+ <result>Event xyz789 | TypeError: null pointer | Breadcrumbs: 5 entries | Tags: browser=Chrome, os=macOS</result>
638
+ </example>
639
+ </examples>
640
+ `.trim();
641
+ var getEventDetailsTool = defineTool({
642
+ name: "get_event_details",
643
+ title: "Get Event Details",
644
+ annotations: { readOnlyHint: true, openWorldHint: true },
645
+ skills: ["triage"],
646
+ requiredScopes: [SCOPE9.EVENT_READ],
647
+ description: DESCRIPTION9,
648
+ inputSchema: z9.object({
649
+ eventId: z9.string().describe("SDK event ID")
650
+ }),
651
+ async handler(params, ctx) {
652
+ if (!params.eventId) throw new UserInputError("eventId is required");
653
+ const event = await ctx.apiClient.getEvent(params.eventId);
654
+ if (!event) {
655
+ return {
656
+ content: [{
657
+ type: "text",
658
+ text: `Event ${params.eventId} not found. Either the ID is incorrect or the Events module (Phase C) is not yet deployed.`
659
+ }]
660
+ };
661
+ }
662
+ return { content: [{ type: "text", text: formatEvent(event) }] };
663
+ }
664
+ });
665
+
666
+ // src/tools/triage/get-event-attachment.ts
667
+ import { z as z10 } from "zod";
668
+ import { SCOPE as SCOPE10 } from "@koderlabs/tasks-sdk-types";
669
+ var DESCRIPTION10 = `
670
+ USE WHEN:
671
+ - You need a binary file attached to an SDK event (minidump, state snapshot,
672
+ custom attachment uploaded by the SDK).
673
+ - You want to read or display a crash attachment in the agent's context.
674
+
675
+ DO NOT USE:
676
+ - For widget bug screenshots \u2014 use get_bug_screenshot instead (it handles
677
+ the ticket \u2192 event \u2192 attachment chain automatically).
678
+ - When you just need event metadata \u2014 use get_event_details.
679
+
680
+ TRIGGER PATTERNS:
681
+ - "download attachment <id> from event <id>"
682
+ - "get the minidump for event <id>"
683
+ - "show the state snapshot attached to crash <id>"
684
+
685
+ DEPENDENCY: Requires Phase C (Events module) on the API.
686
+
687
+ <examples>
688
+ <example>
689
+ <user>Get attachment abc from event xyz.</user>
690
+ <tool>get_event_attachment { "eventId": "xyz", "attachmentId": "abc" }</tool>
691
+ <result>{ mimeType: 'application/octet-stream', dataBase64: '...' }</result>
692
+ </example>
693
+ </examples>
694
+ `.trim();
695
+ var getEventAttachmentTool = defineTool({
696
+ name: "get_event_attachment",
697
+ title: "Get Event Attachment",
698
+ annotations: { readOnlyHint: true, openWorldHint: true },
699
+ skills: ["triage"],
700
+ requiredScopes: [SCOPE10.EVENT_READ],
701
+ description: DESCRIPTION10,
702
+ inputSchema: z10.object({
703
+ eventId: z10.string().describe("SDK event ID"),
704
+ attachmentId: z10.string().describe("Attachment ID from the event details")
705
+ }),
706
+ async handler(params, ctx) {
707
+ if (!params.eventId) throw new UserInputError("eventId is required");
708
+ if (!params.attachmentId) throw new UserInputError("attachmentId is required");
709
+ const result = await ctx.apiClient.getEventAttachment(params.eventId, params.attachmentId);
710
+ if (!result) {
711
+ throw new ApiNotFoundError("Event attachment", `${params.eventId}/${params.attachmentId}`);
712
+ }
713
+ const isImage = result.mimeType.startsWith("image/");
714
+ if (isImage) {
715
+ return {
716
+ content: [{
717
+ type: "image",
718
+ data: bufferToBase64(result.buffer),
719
+ mimeType: result.mimeType
720
+ }]
721
+ };
722
+ }
723
+ return {
724
+ content: [{
725
+ type: "text",
726
+ text: JSON.stringify({
727
+ mimeType: result.mimeType,
728
+ dataBase64: bufferToBase64(result.buffer),
729
+ sizeBytes: result.buffer.byteLength
730
+ })
731
+ }]
732
+ };
733
+ }
734
+ });
735
+
736
+ // src/tools/widget-bug/get-bug-report.ts
737
+ import { z as z11 } from "zod";
738
+ import { SCOPE as SCOPE11 } from "@koderlabs/tasks-sdk-types";
739
+ var DESCRIPTION11 = `
740
+ USE WHEN:
741
+ - A user submits a bug via the InstantTasks widget and you want to see the
742
+ full report: title, description, URL, browser info, and all attached data.
743
+ - You have a ticket key (e.g. "FE-42") and want a summary of the bug report
744
+ including what the user was doing when they hit the bug.
745
+ - Before calling get_bug_screenshot, get_bug_console_logs, or other
746
+ widget_bug tools \u2014 call this first to confirm the ticket exists and
747
+ understand what data is available.
748
+
749
+ DO NOT USE:
750
+ - For non-widget tickets (tasks, stories, epics) \u2014 use get_ticket_details
751
+ instead (it returns the same data, but without bug-report context).
752
+ - When you need the screenshot image specifically \u2014 use get_bug_screenshot
753
+ which returns image bytes.
754
+
755
+ TRIGGER PATTERNS:
756
+ - "show bug report for <ticket key>"
757
+ - "what did the user report in FE-42?"
758
+ - "open bug <key>"
759
+ - "what happened in ticket <key>?"
760
+ - "user submitted bug <key>, what does it say?"
761
+
762
+ <examples>
763
+ <example>
764
+ <user>Show the bug report for FE-99.</user>
765
+ <tool>get_bug_report { "ticketKey": "FE-99" }</tool>
766
+ <result>FE-99: "Checkout button doesn't work" | URL: /checkout | Browser: Chrome 124 | OS: Windows 11 | Screenshot available: yes</result>
767
+ </example>
768
+ </examples>
769
+ `.trim();
770
+ var getBugReportTool = defineTool({
771
+ name: "get_bug_report",
772
+ title: "Get Bug Report",
773
+ annotations: { readOnlyHint: true, openWorldHint: true },
774
+ skills: ["widget_bug"],
775
+ requiredScopes: [SCOPE11.TICKET_READ],
776
+ description: DESCRIPTION11,
777
+ inputSchema: z11.object({
778
+ ticketKey: z11.string().describe('Ticket key in PROJECT-NUMBER format, e.g. "FE-42"')
779
+ }),
780
+ async handler(params, ctx) {
781
+ if (!params.ticketKey) throw new UserInputError("ticketKey is required");
782
+ const ticket = await ctx.apiClient.getBugReport(params.ticketKey);
783
+ if (!ticket) throw new ApiNotFoundError("Bug report ticket", params.ticketKey);
784
+ const hasScreenshot = !!ticket.customFields?.screenshotAttachmentId;
785
+ const hasAnnotations = !!ticket.customFields?.annotationsAttachmentId;
786
+ const hasSdkEvent = !!ticket.customFields?.sdkEventId;
787
+ const sections = [
788
+ formatTicket(ticket),
789
+ "",
790
+ "\u2500\u2500 Attached data \u2500\u2500",
791
+ `Screenshot: ${hasScreenshot ? "yes (call get_bug_screenshot to fetch image)" : "none"}`,
792
+ `Annotations: ${hasAnnotations ? "yes (call get_bug_annotations)" : "none"}`,
793
+ `SDK event data: ${hasSdkEvent ? "yes (console logs, network requests, breadcrumbs available)" : "none"}`
794
+ ];
795
+ return { content: [{ type: "text", text: sections.join("\n") }] };
796
+ }
797
+ });
798
+
799
+ // src/tools/widget-bug/get-bug-screenshot.ts
800
+ import { z as z12 } from "zod";
801
+ import { SCOPE as SCOPE12 } from "@koderlabs/tasks-sdk-types";
802
+ var DESCRIPTION12 = `
803
+ USE WHEN:
804
+ - You want to see the screenshot the user captured when they submitted a bug
805
+ report via the InstantTasks widget.
806
+ - The screenshot will help you understand what was on screen when the bug
807
+ was reported (UI state, error messages, visual glitches).
808
+ - You need visual evidence before suggesting a fix or asking for more info.
809
+
810
+ DO NOT USE:
811
+ - If the model context does not support images \u2014 use get_bug_report for a
812
+ text summary instead.
813
+ - When you haven't confirmed a screenshot exists \u2014 call get_bug_report first
814
+ to check if screenshotAttachmentId is present.
815
+ - For event attachments not linked to widget bugs \u2014 use get_event_attachment.
816
+
817
+ TRIGGER PATTERNS:
818
+ - "show screenshot for <ticket key>"
819
+ - "what did the screen look like when they reported <key>?"
820
+ - "get the screenshot from FE-42"
821
+ - "show me the bug screenshot"
822
+
823
+ RETURN FORMAT:
824
+ Returns an MCP image content block: { type: 'image', mimeType: 'image/png', data: '<base64>' }.
825
+ The model renders this inline. If no screenshot is attached, returns a text explanation.
826
+
827
+ <examples>
828
+ <example>
829
+ <user>Show me the screenshot from FE-99.</user>
830
+ <tool>get_bug_screenshot { "ticketKey": "FE-99" }</tool>
831
+ <result>[image/png inline \u2014 1920x1080 screenshot of checkout page with error toast visible]</result>
832
+ </example>
833
+ </examples>
834
+ `.trim();
835
+ var getBugScreenshotTool = defineTool({
836
+ name: "get_bug_screenshot",
837
+ title: "Get Bug Screenshot",
838
+ annotations: { readOnlyHint: true, openWorldHint: true },
839
+ skills: ["widget_bug"],
840
+ requiredScopes: [SCOPE12.TICKET_READ],
841
+ description: DESCRIPTION12,
842
+ inputSchema: z12.object({
843
+ ticketKey: z12.string().describe('Ticket key in PROJECT-NUMBER format, e.g. "FE-42"')
844
+ }),
845
+ async handler(params, ctx) {
846
+ if (!params.ticketKey) throw new UserInputError("ticketKey is required");
847
+ const result = await ctx.apiClient.getBugScreenshot(params.ticketKey);
848
+ if (!result) {
849
+ return {
850
+ content: [{
851
+ type: "text",
852
+ text: `No screenshot found for ticket ${params.ticketKey}. Either the ticket does not exist, was not submitted via the widget, or no screenshot was captured. Call get_bug_report to check what data is available.`
853
+ }]
854
+ };
855
+ }
856
+ return {
857
+ content: [{
858
+ type: "image",
859
+ data: bufferToBase64(result.buffer),
860
+ mimeType: result.mimeType
861
+ }]
862
+ };
863
+ }
864
+ });
865
+
866
+ // src/tools/widget-bug/get-bug-annotations.ts
867
+ import { z as z13 } from "zod";
868
+ import { SCOPE as SCOPE13 } from "@koderlabs/tasks-sdk-types";
869
+ var DESCRIPTION13 = `
870
+ USE WHEN:
871
+ - The bug reporter drew on the screenshot to highlight a UI element, and you
872
+ want to understand what they were pointing at.
873
+ - You need to know which part of the screen the user considered important.
874
+
875
+ DO NOT USE:
876
+ - As a substitute for get_bug_screenshot \u2014 annotations are overlay commands,
877
+ not a rendered image. Use get_bug_screenshot for the visual.
878
+
879
+ TRIGGER PATTERNS:
880
+ - "what did the user highlight in the screenshot?"
881
+ - "show annotations for <ticket key>"
882
+ - "what was the user pointing at?"
883
+
884
+ <examples>
885
+ <example>
886
+ <user>What did the user highlight in FE-99's screenshot?</user>
887
+ <tool>get_bug_annotations { "ticketKey": "FE-99" }</tool>
888
+ <result>[{ type: "highlight", x: 120, y: 340, width: 200, height: 40, comment: "this button is broken" }]</result>
889
+ </example>
890
+ </examples>
891
+ `.trim();
892
+ var getBugAnnotationsTool = defineTool({
893
+ name: "get_bug_annotations",
894
+ title: "Get Bug Annotations",
895
+ annotations: { readOnlyHint: true, openWorldHint: true },
896
+ skills: ["widget_bug"],
897
+ requiredScopes: [SCOPE13.TICKET_READ],
898
+ description: DESCRIPTION13,
899
+ inputSchema: z13.object({
900
+ ticketKey: z13.string().describe('Ticket key in PROJECT-NUMBER format, e.g. "FE-42"')
901
+ }),
902
+ async handler(params, ctx) {
903
+ if (!params.ticketKey) throw new UserInputError("ticketKey is required");
904
+ const annotations = await ctx.apiClient.getBugAnnotations(params.ticketKey);
905
+ if (annotations === null) {
906
+ return {
907
+ content: [{
908
+ type: "text",
909
+ text: `No annotations found for ticket ${params.ticketKey}. The reporter may not have drawn on the screenshot, or the ticket was not submitted via the widget.`
910
+ }]
911
+ };
912
+ }
913
+ return {
914
+ content: [{
915
+ type: "text",
916
+ text: typeof annotations === "string" ? annotations : JSON.stringify(annotations, null, 2)
917
+ }]
918
+ };
919
+ }
920
+ });
921
+
922
+ // src/tools/widget-bug/get-bug-console-logs.ts
923
+ import { z as z14 } from "zod";
924
+ import { SCOPE as SCOPE14 } from "@koderlabs/tasks-sdk-types";
925
+ var DESCRIPTION14 = `
926
+ USE WHEN:
927
+ - You need to see what JavaScript console output (console.log, console.error,
928
+ console.warn) was present in the browser when the user submitted the bug.
929
+ - Useful for spotting JS errors, API response logging, or debug output that
930
+ reveals the root cause.
931
+
932
+ DO NOT USE:
933
+ - For server-side logs \u2014 console logs here are browser-side only.
934
+ - When the ticket was not submitted via the InstantTasks widget \u2014 the data
935
+ won't be available.
936
+
937
+ TRIGGER PATTERNS:
938
+ - "show console logs for <ticket key>"
939
+ - "what errors appeared in the console when <key> happened?"
940
+ - "console output for bug <key>"
941
+
942
+ <examples>
943
+ <example>
944
+ <user>Show console logs for FE-99.</user>
945
+ <tool>get_bug_console_logs { "ticketKey": "FE-99" }</tool>
946
+ <result>[ERROR] TypeError: Cannot read properties of null | [WARN] Slow API response 3200ms | [LOG] user clicked checkout</result>
947
+ </example>
948
+ </examples>
949
+ `.trim();
950
+ var getBugConsoleLogsTool = defineTool({
951
+ name: "get_bug_console_logs",
952
+ title: "Get Bug Console Logs",
953
+ annotations: { readOnlyHint: true, openWorldHint: true },
954
+ meta: { "anthropic/maxResultSizeChars": 3e5 },
955
+ skills: ["widget_bug"],
956
+ requiredScopes: [SCOPE14.TICKET_READ],
957
+ description: DESCRIPTION14,
958
+ inputSchema: z14.object({
959
+ ticketKey: z14.string().describe('Ticket key in PROJECT-NUMBER format, e.g. "FE-42"'),
960
+ level: z14.enum(["error", "warn", "log", "info", "debug"]).optional().describe("Filter by log level"),
961
+ limit: z14.number().optional().describe("Max entries to return (default: all)")
962
+ }),
963
+ async handler(params, ctx) {
964
+ if (!params.ticketKey) throw new UserInputError("ticketKey is required");
965
+ const logs = await ctx.apiClient.getBugConsoleLogs(params.ticketKey);
966
+ if (logs === null) {
967
+ return {
968
+ content: [{
969
+ type: "text",
970
+ text: `No console logs found for ticket ${params.ticketKey}. The ticket may not have been submitted via the widget, or console recording was disabled.`
971
+ }]
972
+ };
973
+ }
974
+ let filtered = logs;
975
+ if (params.level) {
976
+ filtered = filtered.filter((l) => l.level === params.level);
977
+ }
978
+ if (params.limit) {
979
+ filtered = filtered.slice(0, params.limit);
980
+ }
981
+ if (filtered.length === 0) {
982
+ return { content: [{ type: "text", text: "No console log entries match the filter." }] };
983
+ }
984
+ const lines = filtered.map((l) => {
985
+ const ts = l.timestamp ? `[${l.timestamp}] ` : "";
986
+ const lvl = l.level ? `[${l.level.toUpperCase()}] ` : "";
987
+ return `${ts}${lvl}${l.message ?? JSON.stringify(l)}`;
988
+ });
989
+ return { content: [{ type: "text", text: lines.join("\n") }] };
990
+ }
991
+ });
992
+
993
+ // src/tools/widget-bug/get-bug-network-requests.ts
994
+ import { z as z15 } from "zod";
995
+ import { SCOPE as SCOPE15 } from "@koderlabs/tasks-sdk-types";
996
+ var DESCRIPTION15 = `
997
+ USE WHEN:
998
+ - You need to inspect what API calls the browser made before or during the bug.
999
+ - Useful for spotting failed API calls (4xx/5xx), slow responses, or missing
1000
+ requests that explain the bug.
1001
+
1002
+ DO NOT USE:
1003
+ - When you need server-side request logs \u2014 this is browser-side only.
1004
+ - For tickets not submitted via the InstantTasks widget.
1005
+
1006
+ TRIGGER PATTERNS:
1007
+ - "what API calls were made before bug <key>?"
1008
+ - "show network requests for <ticket key>"
1009
+ - "did any request fail when <key> occurred?"
1010
+ - "show 4xx requests in <key>"
1011
+
1012
+ <examples>
1013
+ <example>
1014
+ <user>Show network requests for FE-99, filtering for failed ones.</user>
1015
+ <tool>get_bug_network_requests { "ticketKey": "FE-99", "statusFilter": "4xx" }</tool>
1016
+ <result>3 failed requests: POST /api/checkout \u2192 422 (150ms), GET /api/cart \u2192 401 (89ms), ...</result>
1017
+ </example>
1018
+ </examples>
1019
+ `.trim();
1020
+ var getBugNetworkRequestsTool = defineTool({
1021
+ name: "get_bug_network_requests",
1022
+ title: "Get Bug Network Requests",
1023
+ annotations: { readOnlyHint: true, openWorldHint: true },
1024
+ meta: { "anthropic/maxResultSizeChars": 3e5 },
1025
+ skills: ["widget_bug"],
1026
+ requiredScopes: [SCOPE15.TICKET_READ],
1027
+ description: DESCRIPTION15,
1028
+ inputSchema: z15.object({
1029
+ ticketKey: z15.string().describe('Ticket key in PROJECT-NUMBER format, e.g. "FE-42"'),
1030
+ statusFilter: z15.string().optional().describe('Filter by status range: "4xx", "5xx", "2xx", or exact code like "404"'),
1031
+ limit: z15.number().optional().describe("Max entries to return (default: all)")
1032
+ }),
1033
+ async handler(params, ctx) {
1034
+ if (!params.ticketKey) throw new UserInputError("ticketKey is required");
1035
+ const requests = await ctx.apiClient.getBugNetworkRequests(params.ticketKey);
1036
+ if (requests === null) {
1037
+ return {
1038
+ content: [{
1039
+ type: "text",
1040
+ text: `No network requests found for ticket ${params.ticketKey}. The ticket may not have been submitted via the widget, or network recording was disabled.`
1041
+ }]
1042
+ };
1043
+ }
1044
+ let filtered = requests;
1045
+ if (params.statusFilter) {
1046
+ const sf = params.statusFilter;
1047
+ if (sf === "4xx") filtered = filtered.filter((r) => (r.status ?? 0) >= 400 && (r.status ?? 0) < 500);
1048
+ else if (sf === "5xx") filtered = filtered.filter((r) => (r.status ?? 0) >= 500);
1049
+ else if (sf === "2xx") filtered = filtered.filter((r) => (r.status ?? 0) >= 200 && (r.status ?? 0) < 300);
1050
+ else {
1051
+ const code = parseInt(sf, 10);
1052
+ if (!isNaN(code)) filtered = filtered.filter((r) => r.status === code);
1053
+ }
1054
+ }
1055
+ if (params.limit) filtered = filtered.slice(0, params.limit);
1056
+ if (filtered.length === 0) {
1057
+ return { content: [{ type: "text", text: "No network requests match the filter." }] };
1058
+ }
1059
+ const lines = filtered.map((r) => {
1060
+ const status = r.status ?? "?";
1061
+ const duration = r.duration != null ? `${r.duration}ms` : "?ms";
1062
+ const size = r.responseSize != null ? ` ${Math.round(r.responseSize / 1024)}KB` : "";
1063
+ return `${r.method ?? "GET"} ${r.url ?? "?"} \u2192 ${status} (${duration}${size})`;
1064
+ });
1065
+ return { content: [{ type: "text", text: `${filtered.length} request(s):
1066
+ ${lines.join("\n")}` }] };
1067
+ }
1068
+ });
1069
+
1070
+ // src/tools/widget-bug/get-bug-metadata.ts
1071
+ import { z as z16 } from "zod";
1072
+ import { SCOPE as SCOPE16 } from "@koderlabs/tasks-sdk-types";
1073
+ var DESCRIPTION16 = `
1074
+ USE WHEN:
1075
+ - You want to know the developer-supplied context attached to a bug report:
1076
+ userId, plan tier, feature flags, environment, custom attributes.
1077
+ - Host apps use widget.setCustomData() to inject structured context \u2014 this
1078
+ tool returns whatever was set.
1079
+
1080
+ DO NOT USE:
1081
+ - For console logs \u2014 use get_bug_console_logs.
1082
+ - For breadcrumbs (user actions) \u2014 use get_bug_breadcrumbs.
1083
+ - For browser/OS info \u2014 use get_bug_report which includes that in the summary.
1084
+
1085
+ TRIGGER PATTERNS:
1086
+ - "what user submitted bug <key>?"
1087
+ - "show metadata for <ticket key>"
1088
+ - "what plan was the user on when they hit <key>?"
1089
+ - "custom data for bug <key>"
1090
+
1091
+ <examples>
1092
+ <example>
1093
+ <user>What user submitted FE-99?</user>
1094
+ <tool>get_bug_metadata { "ticketKey": "FE-99" }</tool>
1095
+ <result>userId: u_12345 | email: alice@example.com | plan: enterprise | featureFlag.newCheckout: true</result>
1096
+ </example>
1097
+ </examples>
1098
+ `.trim();
1099
+ var getBugMetadataTool = defineTool({
1100
+ name: "get_bug_metadata",
1101
+ title: "Get Bug Metadata",
1102
+ annotations: { readOnlyHint: true, openWorldHint: true },
1103
+ skills: ["widget_bug"],
1104
+ requiredScopes: [SCOPE16.TICKET_READ],
1105
+ description: DESCRIPTION16,
1106
+ inputSchema: z16.object({
1107
+ ticketKey: z16.string().describe('Ticket key in PROJECT-NUMBER format, e.g. "FE-42"')
1108
+ }),
1109
+ async handler(params, ctx) {
1110
+ if (!params.ticketKey) throw new UserInputError("ticketKey is required");
1111
+ const metadata = await ctx.apiClient.getBugMetadata(params.ticketKey);
1112
+ if (metadata === null) {
1113
+ return {
1114
+ content: [{
1115
+ type: "text",
1116
+ text: `No developer metadata found for ticket ${params.ticketKey}. The host app may not have called setCustomData(), or the ticket was not submitted via the widget.`
1117
+ }]
1118
+ };
1119
+ }
1120
+ const lines = Object.entries(metadata).map(([k, v]) => `${k}: ${JSON.stringify(v)}`);
1121
+ return { content: [{ type: "text", text: lines.join("\n") || "Metadata object is empty." }] };
1122
+ }
1123
+ });
1124
+
1125
+ // src/tools/widget-bug/get-bug-breadcrumbs.ts
1126
+ import { z as z17 } from "zod";
1127
+ import { SCOPE as SCOPE17 } from "@koderlabs/tasks-sdk-types";
1128
+ var DESCRIPTION17 = `
1129
+ USE WHEN:
1130
+ - You want to reconstruct what the user was doing before they submitted the
1131
+ bug \u2014 which pages they visited, what they clicked, what API calls fired.
1132
+ - You need to understand the reproduction steps for a bug report.
1133
+ - The breadcrumb trail often contains the smoking gun that explains WHY the
1134
+ bug happened.
1135
+
1136
+ DO NOT USE:
1137
+ - For console output (console.log) \u2014 use get_bug_console_logs.
1138
+ - For network request details \u2014 use get_bug_network_requests.
1139
+
1140
+ TRIGGER PATTERNS:
1141
+ - "what was the user doing before bug <key>?"
1142
+ - "show breadcrumbs for <ticket key>"
1143
+ - "user journey for bug <key>"
1144
+ - "how did the user get to the error in <key>?"
1145
+ - "reproduce steps for <key>"
1146
+
1147
+ <examples>
1148
+ <example>
1149
+ <user>What was the user doing before submitting FE-99?</user>
1150
+ <tool>get_bug_breadcrumbs { "ticketKey": "FE-99" }</tool>
1151
+ <result>
1152
+ 14:23:01 [navigation] Navigated to /checkout
1153
+ 14:23:05 [click] Clicked "Place Order" button
1154
+ 14:23:05 [xhr] POST /api/orders \u2192 pending
1155
+ 14:23:08 [error] Unhandled rejection: Network timeout
1156
+ </result>
1157
+ </example>
1158
+ </examples>
1159
+ `.trim();
1160
+ var getBugBreadcrumbsTool = defineTool({
1161
+ name: "get_bug_breadcrumbs",
1162
+ title: "Get Bug Breadcrumbs",
1163
+ annotations: { readOnlyHint: true, openWorldHint: true },
1164
+ meta: { "anthropic/maxResultSizeChars": 2e5 },
1165
+ skills: ["widget_bug"],
1166
+ requiredScopes: [SCOPE17.TICKET_READ],
1167
+ description: DESCRIPTION17,
1168
+ inputSchema: z17.object({
1169
+ ticketKey: z17.string().describe('Ticket key in PROJECT-NUMBER format, e.g. "FE-42"'),
1170
+ limit: z17.number().optional().describe("Max breadcrumbs to return, from most recent (default: all)")
1171
+ }),
1172
+ async handler(params, ctx) {
1173
+ if (!params.ticketKey) throw new UserInputError("ticketKey is required");
1174
+ const breadcrumbs = await ctx.apiClient.getBugBreadcrumbs(params.ticketKey);
1175
+ if (breadcrumbs === null) {
1176
+ return {
1177
+ content: [{
1178
+ type: "text",
1179
+ text: `No breadcrumbs found for ticket ${params.ticketKey}. The ticket may not have been submitted via the widget, or breadcrumb recording was disabled.`
1180
+ }]
1181
+ };
1182
+ }
1183
+ let crumbs = breadcrumbs;
1184
+ if (params.limit) crumbs = crumbs.slice(-params.limit);
1185
+ if (crumbs.length === 0) {
1186
+ return { content: [{ type: "text", text: "No breadcrumbs recorded." }] };
1187
+ }
1188
+ return { content: [{ type: "text", text: formatBreadcrumbs(crumbs) }] };
1189
+ }
1190
+ });
1191
+
1192
+ // src/tools/tickets/create-ticket.ts
1193
+ import { z as z18 } from "zod";
1194
+
1195
+ // src/auth/scopes.ts
1196
+ var ScopeSet = class {
1197
+ set;
1198
+ constructor(scopes) {
1199
+ this.set = new Set(scopes);
1200
+ }
1201
+ has(scope) {
1202
+ return this.set.has(scope);
1203
+ }
1204
+ hasAll(scopes) {
1205
+ return scopes.every((s) => this.set.has(s));
1206
+ }
1207
+ toArray() {
1208
+ return [...this.set];
1209
+ }
1210
+ };
1211
+ function assertScope(scopes, required) {
1212
+ if (!scopes.has(required)) {
1213
+ throw new ScopeError(required);
1214
+ }
1215
+ }
1216
+
1217
+ // src/tools/tickets/create-ticket.ts
1218
+ import { SCOPE as SCOPE18 } from "@koderlabs/tasks-sdk-types";
1219
+
1220
+ // src/middleware/audit.ts
1221
+ var REDACTED_KEYS = /* @__PURE__ */ new Set([
1222
+ "password",
1223
+ "token",
1224
+ "secret",
1225
+ "apiKey",
1226
+ "api_key",
1227
+ "accessToken",
1228
+ "access_token",
1229
+ "refreshToken",
1230
+ "refresh_token",
1231
+ "privateKey",
1232
+ "private_key",
1233
+ "clientSecret",
1234
+ "client_secret",
1235
+ "authorization"
1236
+ ]);
1237
+ var REDACTED_SENTINEL = "[REDACTED]";
1238
+ var SECRET_PATTERNS = [
1239
+ /sk_(?:live|test)_[A-Za-z0-9_-]+/g,
1240
+ /Bearer\s+[A-Za-z0-9._\-+/=]+/gi,
1241
+ /eyJ[A-Za-z0-9._-]{20,}/g
1242
+ // JWT-like
1243
+ ];
1244
+ function redactString(text) {
1245
+ let out = text;
1246
+ for (const re of SECRET_PATTERNS) {
1247
+ out = out.replace(re, REDACTED_SENTINEL);
1248
+ }
1249
+ return out;
1250
+ }
1251
+ function redactArgs(value, depth = 0) {
1252
+ if (depth > 10) return "[DEEP]";
1253
+ if (value === null || value === void 0) return value;
1254
+ if (typeof value === "string") return redactString(value);
1255
+ if (Array.isArray(value)) return value.map((v) => redactArgs(v, depth + 1));
1256
+ if (typeof value === "object") {
1257
+ const out = {};
1258
+ for (const [k, v] of Object.entries(value)) {
1259
+ out[k] = REDACTED_KEYS.has(k) ? REDACTED_SENTINEL : redactArgs(v, depth + 1);
1260
+ }
1261
+ return out;
1262
+ }
1263
+ return value;
1264
+ }
1265
+ function redactResultContent(content) {
1266
+ return content.map((c) => {
1267
+ if (c.type === "text") {
1268
+ return { type: "text", text: redactString(c.text).slice(0, 1e3) };
1269
+ }
1270
+ return { type: "image", data: REDACTED_SENTINEL, mimeType: c.mimeType };
1271
+ });
1272
+ }
1273
+ function withAudit(toolName, handler) {
1274
+ return async (params, ctx) => {
1275
+ const redacted = redactArgs(params);
1276
+ let result;
1277
+ try {
1278
+ result = await handler(params, ctx);
1279
+ } catch (err) {
1280
+ const message = err instanceof Error ? err.message : String(err);
1281
+ void ctx.auditClient.shipAuditRecord({
1282
+ toolName,
1283
+ args: redacted,
1284
+ result: { error: redactString(message) },
1285
+ success: false
1286
+ });
1287
+ throw err;
1288
+ }
1289
+ void ctx.apiClient.shipAuditRecord({
1290
+ toolName,
1291
+ args: redacted,
1292
+ result: { content: redactResultContent(result.content) },
1293
+ success: !result.isError
1294
+ });
1295
+ return result;
1296
+ };
1297
+ }
1298
+
1299
+ // src/tools/tickets/create-ticket.ts
1300
+ var DESCRIPTION18 = `
1301
+ USE WHEN:
1302
+ - An agent has diagnosed a bug and needs to file a ticket.
1303
+ - The user asks to create a new task, bug report, or story.
1304
+ - A monitoring alert should become an actionable ticket.
1305
+ DO NOT USE to update an existing ticket \u2014 use update_ticket.
1306
+ REQUIRED SCOPE: ticket:write
1307
+ `.trim();
1308
+ var createTicketTool = defineTool({
1309
+ name: "create_ticket",
1310
+ title: "Create Ticket",
1311
+ annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: true },
1312
+ skills: ["tickets"],
1313
+ requiredScopes: [SCOPE18.TICKET_WRITE],
1314
+ description: DESCRIPTION18,
1315
+ inputSchema: z18.object({
1316
+ project: z18.string().describe('Project UUID or key (e.g. "FE")'),
1317
+ title: z18.string().describe("Ticket title / summary"),
1318
+ description: z18.string().optional().describe("Ticket description (markdown)"),
1319
+ issueType: z18.string().optional().describe("bug | story | task | subtask | epic"),
1320
+ priority: z18.string().optional().describe("Highest | High | Medium | Low | Lowest"),
1321
+ assigneeId: z18.string().optional().describe("Assignee user ID (UUID)"),
1322
+ sprintId: z18.string().optional().describe("Sprint UUID"),
1323
+ labels: z18.array(z18.string()).optional(),
1324
+ customFields: z18.record(z18.unknown()).optional()
1325
+ }),
1326
+ handler: withAudit("create_ticket", async (params, ctx) => {
1327
+ assertScope(ctx.scopes, SCOPE18.TICKET_WRITE);
1328
+ if (!params.project) throw new UserInputError("project is required");
1329
+ if (!params.title?.trim()) throw new UserInputError("title is required");
1330
+ const body = { title: params.title };
1331
+ if (params.description !== void 0) body.description = params.description;
1332
+ if (params.issueType !== void 0) body.issueType = params.issueType;
1333
+ if (params.priority !== void 0) body.priority = params.priority;
1334
+ if (params.assigneeId !== void 0) body.assigneeId = params.assigneeId;
1335
+ if (params.sprintId !== void 0) body.sprintId = params.sprintId;
1336
+ if (params.labels !== void 0) body.labels = params.labels;
1337
+ if (params.customFields !== void 0) body.customFields = params.customFields;
1338
+ const ticket = await ctx.apiClient.createTicket(params.project, body);
1339
+ return {
1340
+ content: [{
1341
+ type: "text",
1342
+ text: `Ticket **${ticket.key}** created: ${ticket.title}
1343
+ Status: ${ticket.status} | Priority: ${ticket.priority}`
1344
+ }],
1345
+ _meta: {
1346
+ "instanttasks/ticketId": ticket.id,
1347
+ "instanttasks/ticketKey": ticket.key
1348
+ }
1349
+ };
1350
+ })
1351
+ });
1352
+
1353
+ // src/tools/tickets/update-ticket.ts
1354
+ import { z as z19 } from "zod";
1355
+ import { SCOPE as SCOPE19 } from "@koderlabs/tasks-sdk-types";
1356
+ var updateTicketTool = defineTool({
1357
+ name: "update_ticket",
1358
+ title: "Update Ticket",
1359
+ annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: true },
1360
+ skills: ["tickets"],
1361
+ requiredScopes: [SCOPE19.TICKET_WRITE],
1362
+ description: "Update fields (title, description, priority, assignee, sprint, labels, customFields) on an existing ticket. For status changes use transition_ticket. REQUIRED SCOPE: ticket:write",
1363
+ inputSchema: z19.object({
1364
+ ticket: z19.string().describe('Ticket UUID or key (e.g. "FE-42")'),
1365
+ title: z19.string().optional(),
1366
+ description: z19.string().optional().describe("Markdown. Empty string clears it."),
1367
+ priority: z19.string().optional().describe("Highest | High | Medium | Low | Lowest"),
1368
+ assigneeId: z19.string().nullable().optional().describe("User UUID or null to unassign"),
1369
+ sprintId: z19.string().nullable().optional().describe("Sprint UUID or null"),
1370
+ labels: z19.array(z19.string()).optional(),
1371
+ customFields: z19.record(z19.unknown()).optional()
1372
+ }),
1373
+ handler: withAudit("update_ticket", async (params, ctx) => {
1374
+ assertScope(ctx.scopes, SCOPE19.TICKET_WRITE);
1375
+ if (!params.ticket) throw new UserInputError("ticket is required");
1376
+ const { ticket, ...fields } = params;
1377
+ const definedFields = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0));
1378
+ if (Object.keys(definedFields).length === 0) throw new UserInputError("At least one field must be provided");
1379
+ const updated = await ctx.apiClient.updateTicket(ticket, definedFields);
1380
+ return {
1381
+ content: [{ type: "text", text: `Ticket **${updated.key}** updated. Changed: ${Object.keys(definedFields).join(", ")}` }],
1382
+ _meta: {
1383
+ "instanttasks/ticketId": updated.id,
1384
+ "instanttasks/ticketKey": updated.key
1385
+ }
1386
+ };
1387
+ })
1388
+ });
1389
+
1390
+ // src/tools/tickets/transition-ticket.ts
1391
+ import { z as z20 } from "zod";
1392
+ import { SCOPE as SCOPE20 } from "@koderlabs/tasks-sdk-types";
1393
+ var TERMINAL_CATEGORIES = /* @__PURE__ */ new Set(["DONE", "done", "Done", "CLOSED", "closed", "Closed"]);
1394
+ var transitionTicketTool = defineTool({
1395
+ name: "transition_ticket",
1396
+ title: "Transition Ticket Status",
1397
+ annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: true },
1398
+ skills: ["tickets"],
1399
+ requiredScopes: [SCOPE20.TICKET_WRITE],
1400
+ description: `Move a ticket to a new workflow status (e.g. "In Progress", "Done"). Workflow rules enforced server-side.
1401
+ HUMAN-IN-LOOP GUARD: When project.mcpConfig.requireAcknowledgeOnTerminal=true, transitioning to Done/Closed requires acknowledged:true.
1402
+ If you get an acknowledgement error, re-call with acknowledged:true after confirming with the user.
1403
+ REQUIRED SCOPE: ticket:write`,
1404
+ inputSchema: z20.object({
1405
+ ticket: z20.string().describe('Ticket UUID or key (e.g. "FE-42")'),
1406
+ targetStatus: z20.string().describe('Target status name or ID (e.g. "In Progress", "Done")'),
1407
+ acknowledged: z20.boolean().optional().describe("Set true to confirm terminal-status transition when project requires it"),
1408
+ comment: z20.string().optional().describe("Optional comment to attach with the transition")
1409
+ }),
1410
+ handler: withAudit("transition_ticket", async (params, ctx) => {
1411
+ assertScope(ctx.scopes, SCOPE20.TICKET_WRITE);
1412
+ if (!params.ticket) throw new UserInputError("ticket is required");
1413
+ if (!params.targetStatus) throw new UserInputError("targetStatus is required");
1414
+ const transitions = await ctx.apiClient.getTicketTransitions(params.ticket);
1415
+ const matched = transitions.find((t) => t.name === params.targetStatus || t.id === params.targetStatus);
1416
+ const isTerminal = matched ? TERMINAL_CATEGORIES.has(matched.toStatusCategory ?? "") : false;
1417
+ const ticket = await ctx.apiClient.getTicketByKey(params.ticket);
1418
+ if (!ticket) throw new UserInputError(`Ticket not found: ${params.ticket}`);
1419
+ const projectData = ticket.project;
1420
+ const mcpConfig = projectData?.["mcpConfig"] ?? {};
1421
+ const requireAck = mcpConfig["requireAcknowledgeOnTerminal"] === true;
1422
+ if (isTerminal && requireAck && params.acknowledged !== true) {
1423
+ return {
1424
+ isError: true,
1425
+ content: [{
1426
+ type: "text",
1427
+ text: `transition_ticket: "${params.targetStatus}" is a terminal (Done/Closed) status and this project requires explicit acknowledgement. Re-call with acknowledged:true to confirm.`
1428
+ }]
1429
+ };
1430
+ }
1431
+ const body = { targetStatus: params.targetStatus };
1432
+ if (params.comment) body.comment = params.comment;
1433
+ if (params.acknowledged !== void 0) body.acknowledged = params.acknowledged;
1434
+ const updated = await ctx.apiClient.transitionTicket(params.ticket, body);
1435
+ return {
1436
+ content: [{ type: "text", text: `Ticket **${updated.key}** transitioned to **${updated.status}**.` }],
1437
+ _meta: {
1438
+ "instanttasks/ticketId": updated.id,
1439
+ "instanttasks/ticketKey": updated.key,
1440
+ "instanttasks/newStatus": updated.status
1441
+ }
1442
+ };
1443
+ })
1444
+ });
1445
+
1446
+ // src/tools/tickets/add-comment.ts
1447
+ import { z as z21 } from "zod";
1448
+ import { SCOPE as SCOPE21 } from "@koderlabs/tasks-sdk-types";
1449
+ var addCommentTool = defineTool({
1450
+ name: "add_comment",
1451
+ title: "Add Comment",
1452
+ annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: true },
1453
+ skills: ["tickets"],
1454
+ requiredScopes: [SCOPE21.TICKET_WRITE],
1455
+ description: "Add a comment (markdown) to a ticket. Supports internal notes. Use to document investigation findings, root-cause analysis, or agent decisions. REQUIRED SCOPE: ticket:write",
1456
+ inputSchema: z21.object({
1457
+ ticket: z21.string().describe('Ticket UUID or key (e.g. "FE-42")'),
1458
+ body: z21.string().describe("Comment body (markdown)"),
1459
+ internal: z21.boolean().optional().describe("Mark as internal note (default false)")
1460
+ }),
1461
+ handler: withAudit("add_comment", async (params, ctx) => {
1462
+ assertScope(ctx.scopes, SCOPE21.TICKET_WRITE);
1463
+ if (!params.ticket) throw new UserInputError("ticket is required");
1464
+ if (!params.body?.trim()) throw new UserInputError("body is required");
1465
+ const payload = { body: params.body };
1466
+ if (params.internal !== void 0) payload.internal = params.internal;
1467
+ await ctx.apiClient.addComment(params.ticket, payload);
1468
+ const label = params.internal ? "Internal note" : "Comment";
1469
+ return { content: [{ type: "text", text: `${label} added to **${params.ticket}**.` }] };
1470
+ })
1471
+ });
1472
+
1473
+ // src/tools/tickets/link-pr.ts
1474
+ import { z as z22 } from "zod";
1475
+ import { SCOPE as SCOPE22 } from "@koderlabs/tasks-sdk-types";
1476
+ function detectProvider(prUrl) {
1477
+ return prUrl.includes("bitbucket.org") || prUrl.includes("bitbucket.com") ? "bitbucket" : "github";
1478
+ }
1479
+ var linkPrTool = defineTool({
1480
+ name: "link_pr",
1481
+ title: "Link Pull Request",
1482
+ annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: true },
1483
+ skills: ["tickets"],
1484
+ requiredScopes: [SCOPE22.TICKET_WRITE],
1485
+ description: "Attach a GitHub or Bitbucket PR URL to a ticket. Provider auto-detected from URL. Reuses existing github-integrations/bitbucket-integrations services \u2014 no logic duplicated. REQUIRED SCOPE: ticket:write",
1486
+ inputSchema: z22.object({
1487
+ ticket: z22.string().describe('Ticket UUID or key (e.g. "FE-42")'),
1488
+ prUrl: z22.string().url().describe("Full PR URL, e.g. https://github.com/org/repo/pull/123"),
1489
+ provider: z22.enum(["github", "bitbucket"]).optional().describe("VCS provider (auto-detected if omitted)"),
1490
+ title: z22.string().optional().describe("PR title (optional)"),
1491
+ state: z22.enum(["open", "closed", "merged"]).optional()
1492
+ }),
1493
+ handler: withAudit("link_pr", async (params, ctx) => {
1494
+ assertScope(ctx.scopes, SCOPE22.TICKET_WRITE);
1495
+ if (!params.ticket) throw new UserInputError("ticket is required");
1496
+ if (!params.prUrl) throw new UserInputError("prUrl is required");
1497
+ const provider = params.provider ?? detectProvider(params.prUrl);
1498
+ const body = { url: params.prUrl, refType: "pull_request" };
1499
+ if (params.title !== void 0) body.title = params.title;
1500
+ if (params.state !== void 0) body.state = params.state;
1501
+ await ctx.apiClient.linkPr(params.ticket, provider, body);
1502
+ return { content: [{ type: "text", text: `PR linked to **${params.ticket}** (${provider}): ${params.prUrl}` }] };
1503
+ })
1504
+ });
1505
+
1506
+ // src/tools/tickets/promote-issue-to-ticket.ts
1507
+ import { z as z23 } from "zod";
1508
+ import { SCOPE as SCOPE23 } from "@koderlabs/tasks-sdk-types";
1509
+ var promoteIssueToTicketTool = defineTool({
1510
+ name: "promote_issue_to_ticket",
1511
+ title: "Promote Issue to Ticket",
1512
+ annotations: { destructiveHint: true, idempotentHint: false, openWorldHint: true },
1513
+ skills: ["tickets"],
1514
+ requiredScopes: [SCOPE23.TICKET_WRITE],
1515
+ description: "Promote an SDK-captured error/event (issue) into a first-class ticket. All fields optional \u2014 API copies issue title/description if not overridden. Use after investigating an issue via find_issues / get_issue_details. REQUIRED SCOPE: ticket:write",
1516
+ inputSchema: z23.object({
1517
+ issueId: z23.string().describe("SDK issue ID (UUID)"),
1518
+ title: z23.string().optional().describe("Override ticket title"),
1519
+ description: z23.string().optional().describe("Override ticket description (markdown)"),
1520
+ priority: z23.string().optional().describe("Highest | High | Medium | Low | Lowest"),
1521
+ assigneeId: z23.string().optional().describe("Assignee user ID (UUID)"),
1522
+ sprintId: z23.string().optional().describe("Sprint UUID"),
1523
+ labels: z23.array(z23.string()).optional()
1524
+ }),
1525
+ handler: withAudit("promote_issue_to_ticket", async (params, ctx) => {
1526
+ assertScope(ctx.scopes, SCOPE23.TICKET_WRITE);
1527
+ if (!params.issueId) throw new UserInputError("issueId is required");
1528
+ const body = {};
1529
+ if (params.title !== void 0) body.title = params.title;
1530
+ if (params.description !== void 0) body.description = params.description;
1531
+ if (params.priority !== void 0) body.priority = params.priority;
1532
+ if (params.assigneeId !== void 0) body.assigneeId = params.assigneeId;
1533
+ if (params.sprintId !== void 0) body.sprintId = params.sprintId;
1534
+ if (params.labels !== void 0) body.labels = params.labels;
1535
+ const ticket = await ctx.apiClient.promoteIssueToTicket(params.issueId, body);
1536
+ return { content: [{ type: "text", text: `Issue **${params.issueId}** promoted to ticket **${ticket.key}**: ${ticket.title}` }] };
1537
+ })
1538
+ });
1539
+
1540
+ // src/tools/release/pin-release.ts
1541
+ import { z as z24 } from "zod";
1542
+ import { SCOPE as SCOPE24 } from "@koderlabs/tasks-sdk-types";
1543
+ var pinReleaseTool = defineTool({
1544
+ name: "pin_release",
1545
+ title: "Pin Release to Environment",
1546
+ annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: true },
1547
+ skills: ["release"],
1548
+ requiredScopes: [SCOPE24.EVENT_WRITE],
1549
+ description: "Mark a release (version) as the current release for an environment. Affects fingerprint resolution and symbolication baseline. REQUIRED SCOPE: event:write",
1550
+ inputSchema: z24.object({
1551
+ project: z24.string().describe('Project UUID or key (e.g. "FE")'),
1552
+ version: z24.string().describe('Version UUID or name (e.g. "v1.4.2")'),
1553
+ environment: z24.string().optional().describe('Target environment (default: "production")')
1554
+ }),
1555
+ handler: withAudit("pin_release", async (params, ctx) => {
1556
+ assertScope(ctx.scopes, SCOPE24.EVENT_WRITE);
1557
+ if (!params.project) throw new UserInputError("project is required");
1558
+ if (!params.version) throw new UserInputError("version is required");
1559
+ const env = params.environment ?? "production";
1560
+ await ctx.apiClient.pinRelease(params.project, params.version, env);
1561
+ return { content: [{ type: "text", text: `Release **${params.version}** pinned for **${params.project}** / **${env}**.` }] };
1562
+ })
1563
+ });
1564
+
1565
+ // src/tools/release/symbolicate-frame.ts
1566
+ import { z as z25 } from "zod";
1567
+ import { SCOPE as SCOPE25 } from "@koderlabs/tasks-sdk-types";
1568
+ var frameSchema = z25.object({
1569
+ file: z25.string().describe('Minified file path, e.g. "/static/js/main.abc123.js"'),
1570
+ line: z25.number().int().min(1).describe("1-based line number"),
1571
+ column: z25.number().int().min(1).describe("1-based column number"),
1572
+ function: z25.string().optional().describe("Function name from the minified frame")
1573
+ });
1574
+ var symbolicateFrameTool = defineTool({
1575
+ name: "symbolicate_frame",
1576
+ title: "Symbolicate Stack Frame",
1577
+ annotations: { readOnlyHint: true, openWorldHint: true },
1578
+ meta: { "anthropic/maxResultSizeChars": 2e5 },
1579
+ skills: ["release"],
1580
+ requiredScopes: [SCOPE25.EVENT_WRITE],
1581
+ description: "Symbolicate one or more minified stack frames using source-maps for a release. Returns original file, line, column, and function name. Use after receiving a bug report with obfuscated stack traces. REQUIRED SCOPE: event:write",
1582
+ inputSchema: z25.object({
1583
+ project: z25.string().describe("Project UUID or key"),
1584
+ release: z25.string().optional().describe("Release UUID or version name. Omit to use pinned release."),
1585
+ environment: z25.string().optional().describe('Environment for pinned-release resolution (default: "production")'),
1586
+ frames: z25.array(frameSchema).min(1).describe("Minified stack frames to symbolicate")
1587
+ }),
1588
+ handler: withAudit("symbolicate_frame", async (params, ctx) => {
1589
+ assertScope(ctx.scopes, SCOPE25.EVENT_WRITE);
1590
+ if (!params.project) throw new UserInputError("project is required");
1591
+ if (!params.frames?.length) throw new UserInputError("frames array must not be empty");
1592
+ const releaseRef = params.release ?? "pinned";
1593
+ const env = params.environment ?? "production";
1594
+ const result = await ctx.apiClient.symbolicateFrames(params.project, releaseRef, params.frames, env);
1595
+ const frames = result["frames"] ?? [];
1596
+ if (!frames.length) {
1597
+ return { content: [{ type: "text", text: "Symbolication returned no results. Source maps may not be uploaded for this release." }] };
1598
+ }
1599
+ const lines = frames.map((f, i) => {
1600
+ const orig = `${f["originalFile"] ?? "?"}:${f["originalLine"] ?? "?"}:${f["originalColumn"] ?? "?"}`;
1601
+ const fn = f["originalFunction"] ? ` in ${f["originalFunction"]}` : "";
1602
+ return `Frame ${i + 1}: ${orig}${fn}`;
1603
+ });
1604
+ return { content: [{ type: "text", text: lines.join("\n") }] };
1605
+ })
1606
+ });
1607
+
1608
+ // src/tools/release/find-release-artifacts.ts
1609
+ import { z as z26 } from "zod";
1610
+ import { SCOPE as SCOPE26 } from "@koderlabs/tasks-sdk-types";
1611
+ var findReleaseArtifactsTool = defineTool({
1612
+ name: "find_release_artifacts",
1613
+ title: "Find Release Artifacts",
1614
+ annotations: { readOnlyHint: true, openWorldHint: true },
1615
+ meta: { "anthropic/maxResultSizeChars": 2e5 },
1616
+ skills: ["release"],
1617
+ requiredScopes: [SCOPE26.EVENT_READ],
1618
+ description: "List build artifacts (source maps, bundles, DSYM files) attached to a release. Use to verify source maps are present before symbolication. Read-only. REQUIRED SCOPE: event:read",
1619
+ inputSchema: z26.object({
1620
+ project: z26.string().describe("Project UUID or key"),
1621
+ release: z26.string().describe("Release UUID or version name"),
1622
+ type: z26.enum(["sourcemap", "bundle", "dsym", "proguard"]).optional().describe("Filter by artifact type"),
1623
+ limit: z26.number().int().min(1).max(200).optional().describe("Max results (default 50)")
1624
+ }),
1625
+ async handler(params, ctx) {
1626
+ assertScope(ctx.scopes, SCOPE26.EVENT_READ);
1627
+ if (!params.project) throw new UserInputError("project is required");
1628
+ if (!params.release) throw new UserInputError("release is required");
1629
+ const result = await ctx.apiClient.listReleaseArtifacts(params.project, params.release, params.type, params.limit);
1630
+ const items = result["items"] ?? [];
1631
+ if (!items.length) {
1632
+ return {
1633
+ content: [{
1634
+ type: "text",
1635
+ text: `No artifacts found for **${params.project}** release **${params.release}**${params.type ? ` (type: ${params.type})` : ""}.`
1636
+ }]
1637
+ };
1638
+ }
1639
+ return {
1640
+ content: [{
1641
+ type: "text",
1642
+ text: `${items.length} artifact(s) for **${params.project}** release **${params.release}**:
1643
+ ${JSON.stringify(items, null, 2)}`
1644
+ }]
1645
+ };
1646
+ }
1647
+ });
1648
+
1649
+ // src/tools/index.ts
1650
+ var ALL_TOOLS = [
1651
+ // discover (4)
1652
+ whoamiTool,
1653
+ findOrganizationsTool,
1654
+ findProjectsTool,
1655
+ findReleasesTool,
1656
+ // triage (6)
1657
+ findIssuesTool,
1658
+ findTicketsTool,
1659
+ getIssueDetailsTool,
1660
+ getTicketDetailsTool,
1661
+ getEventDetailsTool,
1662
+ getEventAttachmentTool,
1663
+ // widget_bug (7)
1664
+ getBugReportTool,
1665
+ getBugScreenshotTool,
1666
+ getBugAnnotationsTool,
1667
+ getBugConsoleLogsTool,
1668
+ getBugNetworkRequestsTool,
1669
+ getBugMetadataTool,
1670
+ getBugBreadcrumbsTool,
1671
+ // tickets (Phase F — 6)
1672
+ createTicketTool,
1673
+ updateTicketTool,
1674
+ transitionTicketTool,
1675
+ addCommentTool,
1676
+ linkPrTool,
1677
+ promoteIssueToTicketTool,
1678
+ // release (Phase F — 3)
1679
+ pinReleaseTool,
1680
+ symbolicateFrameTool,
1681
+ findReleaseArtifactsTool
1682
+ ];
1683
+ function toolsForScopes(scopeList) {
1684
+ const set = new Set(scopeList);
1685
+ return ALL_TOOLS.filter((t) => t.requiredScopes.every((s) => set.has(s)));
1686
+ }
1687
+
1688
+ // src/server.ts
1689
+ import { McpError as McpError2, ErrorCode as ErrorCode2 } from "@modelcontextprotocol/sdk/types.js";
1690
+ var SERVER_NAME = "instanttasks-mcp";
1691
+ var SERVER_VERSION = "0.1.0";
1692
+ function createMcpServer(getContext) {
1693
+ const server = new Server(
1694
+ { name: SERVER_NAME, version: SERVER_VERSION },
1695
+ {
1696
+ capabilities: {
1697
+ // Per MCP spec 2025-06-18 §Tools: declare `listChanged` explicitly so
1698
+ // clients know whether to subscribe to `notifications/tools/list_changed`.
1699
+ // Our tool registry is static — set false to skip needless wiring.
1700
+ tools: { listChanged: false }
1701
+ }
1702
+ }
1703
+ );
1704
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1705
+ const ctx = await getContext();
1706
+ const available = toolsForScopes(ctx.scopes.toArray());
1707
+ return {
1708
+ tools: available.map((tool) => ({
1709
+ name: tool.name,
1710
+ // Human-readable display name (Anthropic Directory requires this on
1711
+ // every tool — distinct from `name`).
1712
+ title: tool.title,
1713
+ description: tool.description,
1714
+ inputSchema: zodToJsonSchema(tool.inputSchema),
1715
+ // MCP 2025-06-18: annotations advise clients on tool behaviour
1716
+ // (readOnly / destructive / idempotent / openWorld). Optional —
1717
+ // omit when not provided so the field doesn't appear as undefined.
1718
+ ...tool.annotations && { annotations: tool.annotations },
1719
+ // Anthropic-specific `_meta` (e.g. `anthropic/maxResultSizeChars`)
1720
+ // surfaced at tools/list time so Claude clients can adjust per-tool
1721
+ // output thresholds before invoking.
1722
+ ...tool.meta && { _meta: tool.meta }
1723
+ }))
1724
+ };
1725
+ });
1726
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1727
+ const { name, arguments: rawArgs } = request.params;
1728
+ const tool = ALL_TOOLS.find((t) => t.name === name);
1729
+ if (!tool) {
1730
+ throw new McpError2(ErrorCode2.MethodNotFound, `Unknown tool: ${name}`);
1731
+ }
1732
+ const ctx = await getContext();
1733
+ for (const scope of tool.requiredScopes) {
1734
+ if (!ctx.scopes.has(scope)) {
1735
+ throw new McpError2(
1736
+ ErrorCode2.InvalidRequest,
1737
+ `Token missing required scope '${scope}' for tool '${name}'`
1738
+ );
1739
+ }
1740
+ }
1741
+ const parseResult = tool.inputSchema.safeParse(rawArgs ?? {});
1742
+ if (!parseResult.success) {
1743
+ throw new McpError2(
1744
+ ErrorCode2.InvalidParams,
1745
+ `Invalid parameters for tool '${name}': ${parseResult.error.message}`
1746
+ );
1747
+ }
1748
+ const result = await tool.handler(parseResult.data, {
1749
+ apiClient: ctx.apiClient,
1750
+ auditClient: ctx.auditClient,
1751
+ scopes: ctx.scopes
1752
+ });
1753
+ return {
1754
+ content: result.content,
1755
+ isError: result.isError ?? false,
1756
+ // Forward tool-level `_meta` to the MCP response so clients can
1757
+ // surface deep-links / IDs / mimeType hints. Omit when empty so the
1758
+ // field doesn't appear as `undefined` over the wire.
1759
+ ...result._meta && { _meta: result._meta }
1760
+ };
1761
+ });
1762
+ return server;
1763
+ }
1764
+
1765
+ // src/audit-queue.ts
1766
+ import { promises as fs } from "fs";
1767
+ import { dirname, resolve } from "path";
1768
+ var DEFAULT_PATH = process.env.MCP_AUDIT_QUEUE_PATH ?? "./audit-queue.jsonl";
1769
+ var DEFAULT_MAX = parseInt(process.env.MCP_AUDIT_QUEUE_MAX ?? "5000", 10);
1770
+ var AuditQueue = class {
1771
+ path;
1772
+ maxLines;
1773
+ logger;
1774
+ /** Serializes file access so concurrent enqueue / replay can't tear lines. */
1775
+ chain = Promise.resolve();
1776
+ /** When path is `:memory:` we keep a buffer instead of writing to disk. */
1777
+ memory = null;
1778
+ constructor(opts = {}) {
1779
+ this.path = opts.path ?? DEFAULT_PATH;
1780
+ this.maxLines = opts.maxLines ?? DEFAULT_MAX;
1781
+ this.logger = opts.logger ?? console;
1782
+ if (this.path === ":memory:") this.memory = [];
1783
+ }
1784
+ /** Append a payload to the queue. Never throws — durability is best-effort. */
1785
+ enqueue(payload) {
1786
+ return this.run(async () => {
1787
+ const line = JSON.stringify(payload) + "\n";
1788
+ if (this.memory) {
1789
+ this.memory.push(line);
1790
+ while (this.memory.length > this.maxLines) {
1791
+ this.logger.warn("[AuditQueue] dropping oldest record (in-memory cap exceeded)");
1792
+ this.memory.shift();
1793
+ }
1794
+ return;
1795
+ }
1796
+ try {
1797
+ await fs.mkdir(dirname(resolve(this.path)), { recursive: true });
1798
+ const existing = await this.readLines();
1799
+ let next = existing;
1800
+ while (next.length >= this.maxLines) {
1801
+ this.logger.warn("[AuditQueue] dropping oldest record (file cap exceeded)", {
1802
+ path: this.path,
1803
+ cap: this.maxLines
1804
+ });
1805
+ next = next.slice(1);
1806
+ }
1807
+ next.push(line.trimEnd());
1808
+ await fs.writeFile(this.path, next.join("\n") + "\n", "utf8");
1809
+ } catch (err) {
1810
+ this.logger.error("[AuditQueue] enqueue failed (audit record will be lost):", err);
1811
+ }
1812
+ });
1813
+ }
1814
+ /**
1815
+ * Drain the persisted queue by re-shipping each record. Returns counts so the
1816
+ * caller (boot path) can log a summary. Stops on the first failure to preserve
1817
+ * order — back-pressure during a sustained outage.
1818
+ */
1819
+ replay(send) {
1820
+ return this.run(async () => {
1821
+ const lines = await this.readLines();
1822
+ if (lines.length === 0) return { shipped: 0, remaining: 0 };
1823
+ let shipped = 0;
1824
+ const leftover = [];
1825
+ for (let i = 0; i < lines.length; i++) {
1826
+ const line = lines[i];
1827
+ let payload;
1828
+ try {
1829
+ payload = JSON.parse(line);
1830
+ } catch {
1831
+ this.logger.warn("[AuditQueue] dropping unparseable line on replay");
1832
+ continue;
1833
+ }
1834
+ try {
1835
+ await send(payload);
1836
+ shipped += 1;
1837
+ } catch (err) {
1838
+ this.logger.warn("[AuditQueue] replay paused \u2014 line will retry on next boot:", err);
1839
+ leftover.push(...lines.slice(i));
1840
+ break;
1841
+ }
1842
+ }
1843
+ if (this.memory) {
1844
+ this.memory = leftover.map((l) => l + "\n");
1845
+ } else {
1846
+ if (leftover.length === 0) {
1847
+ await fs.rm(this.path, { force: true });
1848
+ } else {
1849
+ await fs.writeFile(this.path, leftover.join("\n") + "\n", "utf8");
1850
+ }
1851
+ }
1852
+ return { shipped, remaining: leftover.length };
1853
+ });
1854
+ }
1855
+ async readLines() {
1856
+ if (this.memory) return this.memory.map((l) => l.trimEnd());
1857
+ try {
1858
+ const buf = await fs.readFile(this.path, "utf8");
1859
+ return buf.split("\n").filter(Boolean);
1860
+ } catch (err) {
1861
+ if (err.code === "ENOENT") return [];
1862
+ throw err;
1863
+ }
1864
+ }
1865
+ /**
1866
+ * Chain async work so file access is serialized. The returned promise
1867
+ * resolves to the inner fn's result; the internal `chain` field is kept
1868
+ * pending so the next caller waits for THIS fn's completion before its
1869
+ * own fn runs. Errors in fn() do not break the chain — they propagate
1870
+ * to the returned promise while `chain` swallows them.
1871
+ */
1872
+ run(fn) {
1873
+ const next = this.chain.then(fn);
1874
+ this.chain = next.then(
1875
+ () => void 0,
1876
+ () => void 0
1877
+ );
1878
+ return next;
1879
+ }
1880
+ };
1881
+
1882
+ // src/api-client.ts
1883
+ var _auditQueue = null;
1884
+ function getAuditQueue() {
1885
+ if (!_auditQueue) _auditQueue = new AuditQueue();
1886
+ return _auditQueue;
1887
+ }
1888
+ async function replayPersistedAuditQueue(send) {
1889
+ return getAuditQueue().replay(send);
1890
+ }
1891
+ var ApiClient = class {
1892
+ baseUrl;
1893
+ headers;
1894
+ constructor(opts) {
1895
+ this.baseUrl = opts.baseUrl.replace(/\/$/, "");
1896
+ this.headers = {
1897
+ Authorization: `Bearer ${opts.token}`,
1898
+ "Content-Type": "application/json",
1899
+ Accept: "application/json"
1900
+ };
1901
+ }
1902
+ // ── helpers ──────────────────────────────────────────────────────────────
1903
+ /**
1904
+ * Unwrap the API's `{ success, data, meta? }` envelope.
1905
+ *
1906
+ * For list endpoints (data: T[]) we expose `{ items, meta }` to match the
1907
+ * historical call-sites that read `result.items`. Single-resource calls
1908
+ * just return the data payload directly.
1909
+ */
1910
+ unwrap(json) {
1911
+ if (json && typeof json === "object" && "success" in json && "data" in json) {
1912
+ const data = json.data;
1913
+ if (Array.isArray(data)) {
1914
+ return { items: data, meta: json.meta };
1915
+ }
1916
+ return data;
1917
+ }
1918
+ return json;
1919
+ }
1920
+ async post(path, body) {
1921
+ let res;
1922
+ try {
1923
+ res = await fetch(`${this.baseUrl}${path}`, {
1924
+ method: "POST",
1925
+ headers: this.headers,
1926
+ body: JSON.stringify(body),
1927
+ signal: AbortSignal.timeout(1e4)
1928
+ });
1929
+ } catch (err) {
1930
+ throw new ApiError(`Network error: ${err.message}`);
1931
+ }
1932
+ if (!res.ok) {
1933
+ const text = await res.text().catch(() => "");
1934
+ throw new ApiError(`${res.status} ${res.statusText}: ${text}`, res.status);
1935
+ }
1936
+ return this.unwrap(await res.json());
1937
+ }
1938
+ async patch(path, body) {
1939
+ let res;
1940
+ try {
1941
+ res = await fetch(`${this.baseUrl}${path}`, {
1942
+ method: "PATCH",
1943
+ headers: this.headers,
1944
+ body: JSON.stringify(body),
1945
+ signal: AbortSignal.timeout(1e4)
1946
+ });
1947
+ } catch (err) {
1948
+ throw new ApiError(`Network error: ${err.message}`);
1949
+ }
1950
+ if (!res.ok) {
1951
+ const text = await res.text().catch(() => "");
1952
+ throw new ApiError(`${res.status} ${res.statusText}: ${text}`, res.status);
1953
+ }
1954
+ return this.unwrap(await res.json());
1955
+ }
1956
+ async get(path, query) {
1957
+ const url = new URL(`${this.baseUrl}${path}`);
1958
+ if (query) {
1959
+ for (const [k, v] of Object.entries(query)) {
1960
+ if (v !== void 0) url.searchParams.set(k, String(v));
1961
+ }
1962
+ }
1963
+ let res;
1964
+ try {
1965
+ res = await fetch(url.toString(), {
1966
+ headers: this.headers,
1967
+ signal: AbortSignal.timeout(1e4)
1968
+ });
1969
+ } catch (err) {
1970
+ throw new ApiError(`Network error reaching InstantTasks API: ${err.message}`);
1971
+ }
1972
+ if (res.status === 404) return null;
1973
+ if (!res.ok) {
1974
+ const text = await res.text().catch(() => "");
1975
+ throw new ApiError(`${res.status} ${res.statusText}: ${text}`, res.status);
1976
+ }
1977
+ return this.unwrap(await res.json());
1978
+ }
1979
+ async getRaw(path) {
1980
+ const url = `${this.baseUrl}${path}`;
1981
+ let res;
1982
+ try {
1983
+ res = await fetch(url, {
1984
+ headers: { Authorization: this.headers.Authorization },
1985
+ signal: AbortSignal.timeout(15e3)
1986
+ });
1987
+ } catch (err) {
1988
+ throw new ApiError(`Network error: ${err.message}`);
1989
+ }
1990
+ if (res.status === 404) return null;
1991
+ if (!res.ok) throw new ApiError(`${res.status} ${res.statusText}`, res.status);
1992
+ const mimeType = res.headers.get("content-type") ?? "application/octet-stream";
1993
+ return { buffer: await res.arrayBuffer(), mimeType };
1994
+ }
1995
+ // ── Discover ─────────────────────────────────────────────────────────────
1996
+ async listOrganizations() {
1997
+ const result = await this.get("/organizations");
1998
+ return result?.items ?? [];
1999
+ }
2000
+ async listProjects(organizationId) {
2001
+ const result = await this.get(`/organizations/${organizationId}/projects`);
2002
+ return result?.items ?? [];
2003
+ }
2004
+ async listReleases(projectId, limit = 20) {
2005
+ const result = await this.get(`/projects/${projectId}/versions`, { limit });
2006
+ return result?.items ?? [];
2007
+ }
2008
+ // ── Triage: Tickets ───────────────────────────────────────────────────────
2009
+ async getTicketByKey(ticketKey) {
2010
+ return this.get(`/tickets/${ticketKey}`);
2011
+ }
2012
+ async listTicketsByIQL(query, projectKey, limit = 25) {
2013
+ const q = { q: query, limit };
2014
+ if (projectKey) q.projectKey = projectKey;
2015
+ const result = await this.get("/search/tickets", q);
2016
+ return result?.items ?? [];
2017
+ }
2018
+ // ── Triage: Issues (Phase C — graceful 404 fallback) ────────────────────
2019
+ /**
2020
+ * List issues in a project.
2021
+ * Returns null if the Issues module is not yet deployed (Phase C dependency).
2022
+ */
2023
+ async listIssues(projectId, filter, limit = 25) {
2024
+ const result = await this.get(`/projects/${projectId}/issues`, {
2025
+ q: filter,
2026
+ limit
2027
+ });
2028
+ return result?.items ?? null;
2029
+ }
2030
+ /**
2031
+ * Get a single issue by ID.
2032
+ * Returns null if not found OR if Phase C not yet merged.
2033
+ */
2034
+ async getIssue(issueId) {
2035
+ return this.get(`/issues/${issueId}`);
2036
+ }
2037
+ /**
2038
+ * Get a single SDK event.
2039
+ * Returns null if not found OR if Phase C not yet merged.
2040
+ */
2041
+ async getEvent(eventId) {
2042
+ return this.get(`/events/${eventId}`);
2043
+ }
2044
+ /**
2045
+ * Get an attachment associated with an event.
2046
+ * Returns raw buffer + mimeType, or null if not found.
2047
+ */
2048
+ async getEventAttachment(eventId, attachmentId) {
2049
+ return this.getRaw(`/events/${eventId}/attachments/${attachmentId}`);
2050
+ }
2051
+ // ── Widget Bug (ticket-level SDK data) ───────────────────────────────────
2052
+ /**
2053
+ * Get the full SDK event payload for a ticket (the "bug report").
2054
+ * Tickets created via the widget SDK have their event data in `customFields`
2055
+ * and a linked attachment. This fetches both.
2056
+ */
2057
+ async getBugReport(ticketKey) {
2058
+ return this.getTicketByKey(ticketKey);
2059
+ }
2060
+ /**
2061
+ * Get the screenshot attachment for a ticket submitted via the widget.
2062
+ * The attachment id is stored in the ticket's `customFields.screenshotAttachmentId`.
2063
+ */
2064
+ async getBugScreenshot(ticketKey) {
2065
+ const ticket = await this.getTicketByKey(ticketKey);
2066
+ if (!ticket) return null;
2067
+ const screenshotId = ticket.customFields?.screenshotAttachmentId;
2068
+ if (!screenshotId) return null;
2069
+ return this.getRaw(`/tickets/${ticket.id}/attachments/${screenshotId}/inline`);
2070
+ }
2071
+ /**
2072
+ * Get the annotations overlay for a ticket screenshot.
2073
+ * Stored as a JSON attachment with mimeType application/json.
2074
+ */
2075
+ async getBugAnnotations(ticketKey) {
2076
+ const ticket = await this.getTicketByKey(ticketKey);
2077
+ if (!ticket) return null;
2078
+ const annotationsId = ticket.customFields?.annotationsAttachmentId;
2079
+ if (!annotationsId) return null;
2080
+ const result = await this.get(`/tickets/${ticket.id}/attachments/${annotationsId}/data`);
2081
+ return result;
2082
+ }
2083
+ /**
2084
+ * Get the console logs captured with the bug report.
2085
+ */
2086
+ async getBugConsoleLogs(ticketKey) {
2087
+ const ticket = await this.getTicketByKey(ticketKey);
2088
+ if (!ticket) return null;
2089
+ const sdkEventId = ticket.customFields?.sdkEventId;
2090
+ if (!sdkEventId) return null;
2091
+ const result = await this.get(`/sdk/events/${sdkEventId}/console-logs`);
2092
+ return result?.consoleLogs ?? null;
2093
+ }
2094
+ /**
2095
+ * Get the network requests captured with the bug report.
2096
+ */
2097
+ async getBugNetworkRequests(ticketKey) {
2098
+ const ticket = await this.getTicketByKey(ticketKey);
2099
+ if (!ticket) return null;
2100
+ const sdkEventId = ticket.customFields?.sdkEventId;
2101
+ if (!sdkEventId) return null;
2102
+ const result = await this.get(`/sdk/events/${sdkEventId}/network-requests`);
2103
+ return result?.networkRequests ?? null;
2104
+ }
2105
+ /**
2106
+ * Get metadata (custom key/value pairs) attached to the bug report.
2107
+ */
2108
+ async getBugMetadata(ticketKey) {
2109
+ const ticket = await this.getTicketByKey(ticketKey);
2110
+ if (!ticket) return null;
2111
+ return ticket.customFields?.sdkMetadata ?? null;
2112
+ }
2113
+ /**
2114
+ * Get the breadcrumb trail leading up to the bug report.
2115
+ */
2116
+ async getBugBreadcrumbs(ticketKey) {
2117
+ const ticket = await this.getTicketByKey(ticketKey);
2118
+ if (!ticket) return null;
2119
+ const sdkEventId = ticket.customFields?.sdkEventId;
2120
+ if (!sdkEventId) return null;
2121
+ const result = await this.get(`/sdk/events/${sdkEventId}/breadcrumbs`);
2122
+ return result?.breadcrumbs ?? null;
2123
+ }
2124
+ // ── Ticket write operations (Phase F) ────────────────────────────────────
2125
+ async createTicket(projectRef, body) {
2126
+ const result = await this.post("/tickets", { ...body, project: projectRef });
2127
+ return result.data;
2128
+ }
2129
+ async updateTicket(ticketRef, fields) {
2130
+ const result = await this.patch(`/tickets/${encodeURIComponent(ticketRef)}`, fields);
2131
+ return result.data;
2132
+ }
2133
+ async transitionTicket(ticketRef, body) {
2134
+ const result = await this.post(
2135
+ `/tickets/${encodeURIComponent(ticketRef)}/transitions`,
2136
+ body
2137
+ );
2138
+ return result.data;
2139
+ }
2140
+ async getTicketTransitions(ticketRef) {
2141
+ const result = await this.get(
2142
+ `/tickets/${encodeURIComponent(ticketRef)}/transitions`
2143
+ );
2144
+ return result?.data ?? [];
2145
+ }
2146
+ async addComment(ticketRef, body) {
2147
+ const result = await this.post(
2148
+ `/tickets/${encodeURIComponent(ticketRef)}/comments`,
2149
+ body
2150
+ );
2151
+ return result.data;
2152
+ }
2153
+ async linkPr(ticketRef, provider, body) {
2154
+ const path = provider === "bitbucket" ? `/tickets/${encodeURIComponent(ticketRef)}/bitbucket-refs` : `/tickets/${encodeURIComponent(ticketRef)}/github-refs`;
2155
+ const result = await this.post(path, body);
2156
+ return result.data;
2157
+ }
2158
+ async promoteIssueToTicket(issueId, body) {
2159
+ const result = await this.post(
2160
+ `/sdk/issues/${encodeURIComponent(issueId)}/promote`,
2161
+ body
2162
+ );
2163
+ return result.data;
2164
+ }
2165
+ // ── Release write operations (Phase F) ───────────────────────────────────
2166
+ async pinRelease(projectRef, versionRef, environment) {
2167
+ const result = await this.patch(
2168
+ `/projects/${encodeURIComponent(projectRef)}/versions/${encodeURIComponent(versionRef)}/pin`,
2169
+ { environment }
2170
+ );
2171
+ return result.data;
2172
+ }
2173
+ async symbolicateFrames(projectRef, releaseRef, frames, environment) {
2174
+ const result = await this.post(
2175
+ `/projects/${encodeURIComponent(projectRef)}/releases/${encodeURIComponent(releaseRef)}/symbolicate`,
2176
+ { frames, environment }
2177
+ );
2178
+ return result.data;
2179
+ }
2180
+ async listReleaseArtifacts(projectRef, releaseRef, type, limit) {
2181
+ const result = await this.get(
2182
+ `/projects/${encodeURIComponent(projectRef)}/releases/${encodeURIComponent(releaseRef)}/artifacts`,
2183
+ { type, limit }
2184
+ );
2185
+ return result?.data ?? result ?? {};
2186
+ }
2187
+ async shipAuditRecord(payload) {
2188
+ await shipAuditWithRetry(
2189
+ (body) => this.post("/audit/mcp", body),
2190
+ payload
2191
+ );
2192
+ }
2193
+ };
2194
+ var DEFAULT_AUDIT_RETRY = {
2195
+ // 3 retries total => 3 attempts; pre-attempt waits are [0, 1s, 3s, 9s]-style.
2196
+ // We use 3 attempts with delays BEFORE retry #2 and retry #3 of 1s and 3s,
2197
+ // and a final 9s back-pressure pause before giving up — matching the
2198
+ // spec's 1s/3s/9s schedule.
2199
+ maxAttempts: 3,
2200
+ delaysMs: [1e3, 3e3, 9e3],
2201
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
2202
+ logger: console
2203
+ };
2204
+ function isRetryable(err) {
2205
+ if (err instanceof ApiError) {
2206
+ if (err.statusCode === void 0) return true;
2207
+ return err.statusCode >= 500;
2208
+ }
2209
+ return true;
2210
+ }
2211
+ async function shipAuditWithRetry(send, payload, options = {}) {
2212
+ const opts = { ...DEFAULT_AUDIT_RETRY, ...options };
2213
+ const delays = opts.delaysMs;
2214
+ let lastErr;
2215
+ for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
2216
+ try {
2217
+ await send(payload);
2218
+ return;
2219
+ } catch (err) {
2220
+ lastErr = err;
2221
+ if (!isRetryable(err) || attempt === opts.maxAttempts) {
2222
+ break;
2223
+ }
2224
+ const delay = delays[attempt - 1] ?? delays[delays.length - 1] ?? 1e3;
2225
+ await opts.sleep(delay);
2226
+ }
2227
+ }
2228
+ const correlationId = payload["toolName"] ?? "unknown";
2229
+ opts.logger.error("[ApiClient] audit/mcp ship failed after retries:", {
2230
+ correlationId,
2231
+ payload,
2232
+ error: lastErr instanceof Error ? lastErr.message : String(lastErr)
2233
+ });
2234
+ void getAuditQueue().enqueue(payload);
2235
+ }
2236
+
2237
+ // src/auth/secret-key.ts
2238
+ function extractBearerToken(authHeader) {
2239
+ if (!authHeader?.startsWith("Bearer ")) return null;
2240
+ const token = authHeader.slice(7).trim();
2241
+ return token || null;
2242
+ }
2243
+ async function validateSecretKey(token, apiBaseUrl) {
2244
+ if (!token.startsWith("sk_live_") && !token.startsWith("sk_test_")) {
2245
+ throw new AuthError("invalid_token", "expected sk_live_* or sk_test_*");
2246
+ }
2247
+ const url = `${apiBaseUrl}/sdk/keys/whoami-management`;
2248
+ let res;
2249
+ try {
2250
+ res = await fetch(url, {
2251
+ method: "GET",
2252
+ headers: { Authorization: `Bearer ${token}` },
2253
+ signal: AbortSignal.timeout(5e3)
2254
+ });
2255
+ } catch (err) {
2256
+ throw new ApiError(`Cannot reach InstantTasks API: ${err.message}`);
2257
+ }
2258
+ if (!res.ok) {
2259
+ throw new AuthError("invalid_token", `whoami-management returned ${res.status}`);
2260
+ }
2261
+ let body;
2262
+ try {
2263
+ body = await res.json();
2264
+ } catch (err) {
2265
+ throw new ApiError(`Malformed whoami-management response: ${err.message}`);
2266
+ }
2267
+ if (!body.id || !body.projectId || !body.organizationId || !Array.isArray(body.scopes)) {
2268
+ throw new AuthError("invalid_token", "whoami-management response missing required fields");
2269
+ }
2270
+ return {
2271
+ keyId: body.id,
2272
+ projectId: body.projectId,
2273
+ organizationId: body.organizationId,
2274
+ scopes: new ScopeSet(body.scopes),
2275
+ kind: "management"
2276
+ };
2277
+ }
2278
+
2279
+ export {
2280
+ ApiError,
2281
+ AuthError,
2282
+ ScopeSet,
2283
+ createMcpServer,
2284
+ replayPersistedAuditQueue,
2285
+ ApiClient,
2286
+ extractBearerToken,
2287
+ validateSecretKey
2288
+ };
2289
+ //# sourceMappingURL=chunk-ULHBL6XY.js.map