Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHALLENGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This repository explains the challenge for internship candidates applying to Lam

Candidates are asked to choose a **unique problem statement**, build a practical contribution, and share it through a pull request and a short walkthrough video.

The goal is not to make the biggest project possible. The goal is to make something focused, useful, and well explained. A strong submission shows good judgment, clear thinking, and enough polish for someone else to understand it quickly.
The goal is not to make the biggest project possible. The goal is to make something focused, useful, and well explained. A strong submission shows good judgment, clear thinking, and enough polish for someone else to understax nd it quickly.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix typo in documentation.

The phrase "understax nd it quickly" contains a typo and should be "understand it quickly". This makes the documentation unprofessional and harder to read.

Additionally, this file modification appears unrelated to the PR's stated purpose of adding a Support Triage Agent kit. Please verify whether this change was intentional.

📝 Proposed fix
-The goal is not to make the biggest project possible. The goal is to make something focused, useful, and well explained. A strong submission shows good judgment, clear thinking, and enough polish for someone else to understax nd it quickly.
+The goal is not to make the biggest project possible. The goal is to make something focused, useful, and well explained. A strong submission shows good judgment, clear thinking, and enough polish for someone else to understand it quickly.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The goal is not to make the biggest project possible. The goal is to make something focused, useful, and well explained. A strong submission shows good judgment, clear thinking, and enough polish for someone else to understax nd it quickly.
The goal is not to make the biggest project possible. The goal is to make something focused, useful, and well explained. A strong submission shows good judgment, clear thinking, and enough polish for someone else to understand it quickly.
🧰 Tools
🪛 LanguageTool

[grammar] ~15-~15: Ensure spelling is correct
Context: ..., and enough polish for someone else to understax nd it quickly. ## ✅ What a strong submiss...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


## ✅ What a strong submission looks like

Expand Down
34 changes: 34 additions & 0 deletions kits/automation/support-triage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
.env

Comment on lines +25 to +28
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Harden env-file ignores to prevent accidental secret commits

Line 25–28 currently miss variants like .env.production / .env.development. That can leak credentials if those files are used. Prefer ignoring all .env* and explicitly allow only .env.example.

Proposed fix
-# local env files
-.env*.local
-.env
+# local env files
+.env*
+!.env.example
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# local env files
.env*.local
.env
# local env files
.env*
!.env.example

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
1 change: 1 addition & 0 deletions kits/automation/support-triage/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
force=true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Committed .npmrc files under support-triage:"
fd '^\.npmrc$' kits/automation/support-triage -x sh -c 'echo "--- $1"; cat "$1"' sh {}

echo
echo "Other explicit force usage under the kit:"
rg -n --hidden --glob '!**/node_modules/**' '\bforce\s*=\s*true\b|\bnpm\b.*\s--force\b' kits/automation/support-triage || true

Repository: Lamatic/AgentKit

Length of output: 329


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching for .npmrc files under support-triage (including hidden files):"
fd -H '\.npmrc$' kits/automation/support-triage -x sh -c 'echo "File: $1"; cat "$1"' sh {} || echo "No .npmrc found"

echo
echo "Checking for npm force-related configurations:"
rg -H -n 'force\s*=\s*true|npm.*--force' kits/automation/support-triage --glob '!**/node_modules/**' || echo "No force configurations found"

Repository: Lamatic/AgentKit

Length of output: 266


🌐 Web query:

npm force parameter .npmrc behavior what does force=true do

💡 Result:

The force=true parameter in an .npmrc file (or --force CLI flag, or npm_config_force=true environment variable) sets npm's force configuration to true. This removes various protections against side effects, mistakes, performance issues, and malicious input. Specific behaviors include: - Allows unpublishing of entire packages (not just single versions). - Allows clobbering existing values in npm pkg set. - Implicitly sets --yes during npm init. - Allows installing packages with conflicting peerDependencies in the root project. - Allows npm audit fix to install modules outside stated dependency ranges (including SemVer-major changes). - Overrides engine-strict to allow installing packages requiring different Node.js versions. - Lifecycle script failures do not block progress. - Publishing clobbers previously published versions. - Skips cache when requesting from the registry. - Prevents checks against clobbering non-npm files. It can be set in project .npmrc, user ~/.npmrc, global config, or via CLI/env. Using force=true triggers a warning "Recommended protections disabled" on many commands, which can be suppressed with loglevel=error in .npmrc. Default is false; use cautiously as it bypasses safety checks.

Citations:


Remove force=true from the .npmrc file.

This project-level setting applies to all npm invocations in the kit and bypasses critical safety checks. Specifically, it allows installations with conflicting peerDependencies and overrides engine-strict, masking dependency incompatibilities that should fail fast. Keep --force as an explicit one-off CLI override when truly needed instead of a checked-in default.

Proposed fix
-force=true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
force=true

79 changes: 79 additions & 0 deletions kits/automation/support-triage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Support Triage Agent

**Support Triage Agent** is a Lamatic-powered automation kit that helps support teams review incoming tickets faster and more consistently. It accepts raw support requests, classifies the issue, assigns severity, estimates duplicate risk, recommends the likely owner, flags SLA risk, and returns a concise escalation summary.

## Problem It Solves

Support teams often spend too much time manually reviewing noisy inbound tickets before any real troubleshooting begins. This kit turns one support request into a structured triage result that is easier to route, prioritize, and escalate.

## What The Kit Returns

For each ticket, the flow returns:

- `category`
- `severity`
- `priority_reason`
- `possible_duplicate`
- `recommended_owner`
- `sla_risk`
- `escalation_summary`

## Lamatic Setup

1. Create a project in [Lamatic](https://lamatic.ai).
2. Create and deploy a flow named `support-triage`.
3. Copy the deployed flow ID and your Lamatic API credentials.
4. Export the flow into [`flows/support-triage`](/home/kumarsaurabh27d/lamatic/AgentKit/kits/automation/support-triage/flows/support-triage).

## Environment Variables

Create `.env.local` with:

```env
FLOW_SUPPORT_TRIAGE="your-flow-id"
LAMATIC_API_URL="https://api.lamatic.ai/v1/..."
LAMATIC_PROJECT_ID="proj_xxxxxxxxxxxx"
LAMATIC_API_KEY="lam_xxxxxxxxxxxx"
```

## Local Development

```bash
npm install
cp .env.example .env.local
npm run dev
```

Open `http://localhost:3000`.

## Example Input

```json
{
"ticket_text": "Our enterprise team cannot access the dashboard and customers are reporting failures.",
"customer_tier": "enterprise",
"channel": "email",
"created_at": "2026-03-21T10:30:00Z",
"past_ticket_context": "Two similar dashboard timeout complaints were reported earlier today."
}
```

## Example Output

```json
{
"category": "Access / Dashboard Issue",
"severity": "high",
"priority_reason": "Enterprise customer reports a customer-facing access failure.",
"possible_duplicate": true,
"recommended_owner": "Platform Support",
"sla_risk": true,
"escalation_summary": "Enterprise customer reports dashboard access failures affecting end users. Similar timeout complaints were seen earlier today. Immediate investigation is recommended."
}
```

## Repository Notes

- Main kit UI: [`app/page.tsx`](/home/kumarsaurabh27d/lamatic/AgentKit/kits/automation/support-triage/app/page.tsx)
- Server action: [`actions/orchestrate.ts`](/home/kumarsaurabh27d/lamatic/AgentKit/kits/automation/support-triage/actions/orchestrate.ts)
- Exported Lamatic flow: [`flows/support-triage/config.json`](/home/kumarsaurabh27d/lamatic/AgentKit/kits/automation/support-triage/flows/support-triage/config.json)
93 changes: 93 additions & 0 deletions kits/automation/support-triage/actions/orchestrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"use server"

import { lamaticClient } from "@/lib/lamatic-client"
import { config } from "../orchestrate.js"

type SchemaToType<T> = {
[K in keyof T]: T[K] extends "string"
? string
: T[K] extends "number"
? number
: T[K] extends "boolean"
? boolean
: never
}

type SupportTriageInput = SchemaToType<typeof config.flows.supportTriage.inputSchema>
type SupportTriageOutput = SchemaToType<typeof config.flows.supportTriage.outputSchema>

const supportTriageFlow = config.flows.supportTriage

function parseBoolean(value: unknown) {
if (typeof value === "boolean") {
return value
}

if (typeof value === "string") {
return value.toLowerCase() === "true"
}

return false
}
Comment on lines +21 to +31
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject invalid workflow output instead of silently coercing it.

Lines 21-31 turn anything other than exact true into false, and Lines 67-73 accept any severity string. That hides schema drift as a successful triage result and can under-prioritize tickets. Validate the booleans and enforce the low|medium|high|critical contract before returning success: true.

Suggested fix
-function parseBoolean(value: unknown) {
+function parseBoolean(value: unknown): boolean | null {
   if (typeof value === "boolean") {
     return value
   }
 
   if (typeof value === "string") {
-    return value.toLowerCase() === "true"
+    const normalized = value.trim().toLowerCase()
+    if (normalized === "true") return true
+    if (normalized === "false") return false
   }
 
-  return false
+  return null
 }
@@
-    return {
+    const severity = String(result.severity ?? "").toLowerCase()
+    const possibleDuplicate = parseBoolean(result.possible_duplicate)
+    const slaRisk = parseBoolean(result.sla_risk)
+
+    if (
+      !["low", "medium", "high", "critical"].includes(severity) ||
+      possibleDuplicate === null ||
+      slaRisk === null
+    ) {
+      throw new Error("Workflow returned an invalid triage payload.")
+    }
+
+    return {
       success: true,
       result: {
         category: String(result.category ?? ""),
-        severity: String(result.severity ?? ""),
+        severity,
         priority_reason: String(result.priority_reason ?? ""),
-        possible_duplicate: parseBoolean(result.possible_duplicate),
+        possible_duplicate: possibleDuplicate,
         recommended_owner: String(result.recommended_owner ?? ""),
-        sla_risk: parseBoolean(result.sla_risk),
+        sla_risk: slaRisk,
         escalation_summary: String(result.escalation_summary ?? ""),
       },
     }

Also applies to: 64-74


export async function executeSupportTriage(
input: SupportTriageInput,
): Promise<{
success: boolean
result?: SupportTriageOutput
error?: string
}> {
try {
if (!supportTriageFlow.workflowId) {
throw Error("Workflow not found in config.")
}

const workflowInput = Object.keys(supportTriageFlow.inputSchema).reduce(
(acc, key) => {
acc[key] = input[key as keyof SupportTriageInput]
return acc
},
{} as Record<string, unknown>,
)

const response = await lamaticClient.executeFlow(supportTriageFlow.workflowId, workflowInput)
const rawResult = response?.result
const result =
rawResult?.output ??
rawResult?.result ??
rawResult

if (!result) {
throw new Error(`No result returned from workflow. Response shape: ${JSON.stringify(response)}`)
Comment on lines +60 to +61
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep raw Lamatic payloads out of returned errors.

Line 61 embeds the whole workflow response in the thrown error, and Lines 76-90 return that message to the browser on the generic path. If Lamatic echoes ticket text or internal metadata, this leaks it directly into the UI. Throw a generic message and log only sanitized diagnostics server-side.

Suggested fix
-    if (!result) {
-      throw new Error(`No result returned from workflow. Response shape: ${JSON.stringify(response)}`)
-    }
+    if (!result) {
+      console.error("Support triage workflow returned no result", {
+        workflowId: supportTriageFlow.workflowId,
+        hasResponse: Boolean(response),
+        hasRawResult: Boolean(rawResult),
+      })
+      throw new Error("Support triage workflow returned no result.")
+    }

Also applies to: 76-90

}

return {
success: true,
result: {
category: String(result.category ?? ""),
severity: String(result.severity ?? ""),
priority_reason: String(result.priority_reason ?? ""),
possible_duplicate: parseBoolean(result.possible_duplicate),
recommended_owner: String(result.recommended_owner ?? ""),
sla_risk: parseBoolean(result.sla_risk),
escalation_summary: String(result.escalation_summary ?? ""),
},
}
} catch (error) {
let errorMessage = "Unknown error occurred"
if (error instanceof Error) {
errorMessage = error.message
if (error.message.includes("fetch failed")) {
errorMessage =
"Network error: Unable to connect to the service. Please check your internet connection and try again."
} else if (error.message.includes("API key")) {
errorMessage = "Authentication error: Please check your API configuration."
}
}

return {
success: false,
error: errorMessage,
}
}
}
125 changes: 125 additions & 0 deletions kits/automation/support-triage/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
@import 'tailwindcss';
@import 'tw-animate-css';

@custom-variant dark (&:is(.dark *));

:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}

.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}

@theme inline {
--font-sans: 'Geist', 'Geist Fallback';
--font-mono: 'Geist Mono', 'Geist Mono Fallback';
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}

@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
45 changes: 45 additions & 0 deletions kits/automation/support-triage/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import { Analytics } from '@vercel/analytics/next'
import './globals.css'

const _geist = Geist({ subsets: ["latin"] });
const _geistMono = Geist_Mono({ subsets: ["latin"] });

export const metadata: Metadata = {
title: 'v0 App',
description: 'Created with v0',
generator: 'v0.app',
icons: {
icon: [
{
url: '/icon-light-32x32.png',
media: '(prefers-color-scheme: light)',
},
{
url: '/icon-dark-32x32.png',
media: '(prefers-color-scheme: dark)',
},
{
url: '/icon.svg',
type: 'image/svg+xml',
},
],
apple: '/apple-icon.png',
},
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className={`font-sans antialiased`}>
{children}
<Analytics />
</body>
</html>
)
}
Loading