# Actions Specification (v2)

## Overview

Actions are **highly specific, executable operations** that users can trigger with one click in the chat interface. Actions are deeply personalized to the user's actual workflow, vendors, customers, and business processes.

Examples:
- ✅ "Send payment reminder to ACME Corp for Invoice #2024-001 (15 days overdue)"
- ✅ "Forward bug reports to engineering team with priority assessment"
- ✅ "Archive newsletters from TechCrunch, Morning Brew, and Hacker News older than 30 days"
- ❌ "Send email" (too generic)
- ❌ "Archive emails" (too generic)

Actions use a **hybrid template + instantiation model**: templates define user-specific patterns with handler functions, and the chat agent instantiates them with precise parameters during conversation.

## Core Principles

- **User-Specific**: Templates are tailored to the user's specific workflows, not generic operations
- **Highly Contextual**: Actions are precise and context-rich, not vague (e.g., "Send payment reminder to ACME Corp for Invoice #2024-001" not "Send email")
- **One-Click Execution**: Clicking an action directly executes code with all parameters pre-filled
- **Template-Based**: User-specific templates instantiated with precise parameters by the agent
- **Full Capabilities**: Access to email operations, AI, and external APIs
- **Fully Logged**: All executions tracked for audit trail and debugging

## Architecture

### Directory Structure

```
agent/custom_scripts/
├── actions/
│   ├── send-payment-reminder-to-acme.ts          # Send payment reminder to ACME Corp
│   ├── archive-old-newsletters.ts                # Archive newsletters older than 30 days
│   ├── forward-bugs-to-engineering.ts            # Forward bug reports to engineering team
│   ├── summarize-weekly-updates-from-ceo.ts      # Summarize CEO's weekly updates
│   └── label-urgent-customer-support.ts          # Label urgent customer support emails
├── types.ts                                      # Shared type definitions
└── .logs/
    └── actions/                                  # Action execution logs (JSONL)
```

### Template File Structure

Each action template exports:
1. **`config`** - Template metadata and parameter schema
2. **`handler`** - Async function that executes the action

```typescript
// agent/custom_scripts/actions/example-action.ts
import type { ActionTemplate, ActionContext, ActionResult } from "../types";

export const config: ActionTemplate = {
  id: "example_action",
  name: "Example Action Template",
  description: "What this template does",
  icon: "🚀",
  parameterSchema: {
    // JSON schema for required parameters
    type: "object",
    properties: {
      recipient: { type: "string", description: "Email recipient" },
      subject: { type: "string", description: "Email subject" }
    },
    required: ["recipient"]
  }
};

export async function handler(
  params: Record<string, any>,
  context: ActionContext
): Promise<ActionResult> {
  // Execute action logic using context methods
  // Return structured result

  return {
    success: true,
    message: "Action completed successfully",
    data: { /* optional structured data */ }
  };
}
```

## Type Definitions

```typescript
// agent/custom_scripts/types.ts

/**
 * Action Template Definition
 * Defines a reusable action pattern that can be instantiated with parameters
 */
export interface ActionTemplate {
  // Unique identifier for this action template
  id: string;

  // Human-readable name
  name: string;

  // Description of what this template does
  description: string;

  // Optional emoji or icon
  icon?: string;

  // JSON Schema defining required/optional parameters
  parameterSchema: {
    type: "object";
    properties: Record<string, {
      type: string;
      description: string;
      enum?: string[];
      default?: any;
    }>;
    required?: string[];
  };
}

/**
 * Action Instance
 * Created by agent during conversation by providing parameters to a template
 */
export interface ActionInstance {
  // Unique ID for this specific action instance
  instanceId: string;

  // Reference to template ID
  templateId: string;

  // Label shown on button (generated by agent)
  label: string;

  // Optional description (more context)
  description?: string;

  // Parameters to pass to handler
  params: Record<string, any>;

  // Button styling
  style?: "primary" | "secondary" | "danger";

  // Session ID this action belongs to
  sessionId: string;

  // Timestamp when created
  createdAt: string;
}

/**
 * Action Context
 * Provides capabilities to action handlers (similar to ListenerContext)
 */
export interface ActionContext {
  // Session information
  sessionId: string;

  // Email API operations
  emailAPI: {
    getInbox(options?: { limit?: number; includeRead?: boolean }): Promise<Email[]>;
    searchEmails(criteria: EmailSearchCriteria): Promise<Email[]>;
    searchWithGmailQuery(query: string): Promise<Email[]>;
    getEmailsByIds(ids: string[]): Promise<Email[]>;
    getEmailById(id: string): Promise<Email | null>;
  };

  // Direct email operations (similar to listeners)
  archiveEmail(emailId: string): Promise<void>;
  starEmail(emailId: string): Promise<void>;
  unstarEmail(emailId: string): Promise<void>;
  markAsRead(emailId: string): Promise<void>;
  markAsUnread(emailId: string): Promise<void>;
  addLabel(emailId: string, label: string): Promise<void>;
  removeLabel(emailId: string, label: string): Promise<void>;

  // Send emails
  sendEmail(options: {
    to: string;
    subject: string;
    body: string;
    cc?: string;
    bcc?: string;
    replyTo?: string;
  }): Promise<{ messageId: string }>;

  // AI/Agent capabilities
  callAgent<T = any>(options: {
    prompt: string;
    systemPrompt?: string;
    tools?: string[];
    maxTokens?: number;
  }): Promise<T>;

  // Session messaging (inject messages into chat)
  addUserMessage(content: string): void;
  addAssistantMessage(content: string): void;
  addSystemMessage(content: string): void;

  // Notifications
  notify(message: string, options?: {
    priority?: "low" | "normal" | "high";
    type?: "info" | "success" | "warning" | "error";
  }): void;

  // External API access
  fetch(url: string, options?: RequestInit): Promise<Response>;

  // Logging
  log(message: string, level?: "info" | "warn" | "error"): void;
}

/**
 * Action Result
 * Returned by handler to indicate execution outcome
 */
export interface ActionResult {
  // Whether action succeeded
  success: boolean;

  // Human-readable message describing result
  message: string;

  // Optional structured data to return
  data?: Record<string, any>;

  // Optional array of follow-up actions to suggest
  suggestedActions?: ActionInstance[];

  // Whether to refresh inbox after this action
  refreshInbox?: boolean;
}

/**
 * Action Log Entry
 * Stored in .logs/actions/ for audit trail
 */
export interface ActionLogEntry {
  timestamp: string;
  instanceId: string;
  templateId: string;
  sessionId: string;
  params: Record<string, any>;
  result: ActionResult;
  duration: number; // milliseconds
  error?: string;
}
```

## Action Lifecycle

### 1. Agent Creates Action Instance

During chat conversation, agent identifies need for specific action:

```typescript
// Agent's internal process (conceptual)
// User: "I need to follow up on the ACME Corp invoice payment"
// Agent searches emails, finds Invoice #2024-001 is 15 days overdue

// Agent creates action instances via special format in chat response
{
  type: "actions",
  actions: [
    {
      instanceId: "act_abc123",
      templateId: "send_payment_reminder_acme",
      label: "Send payment reminder to ACME Corp for Invoice #2024-001",
      description: "Invoice #2024-001 ($5,000) is 15 days past due",
      params: {
        invoiceNumber: "INV-2024-001",
        amount: "$5,000",
        dueDate: "December 15, 2024",
        daysPastDue: 15
      },
      style: "primary",
      sessionId: "session_xyz",
      createdAt: "2025-01-06T10:30:00Z"
    }
  ]
}
```

### 2. Frontend Renders Action Buttons

Client receives action instances via WebSocket and renders as interactive buttons:

```typescript
// Message type from server → client
{
  type: "action_instances",
  actions: ActionInstance[],
  sessionId: string
}
```

### 3. User Clicks Action

Frontend sends execution request:

```typescript
// Message type from client → server
{
  type: "execute_action",
  instanceId: "act_abc123",
  sessionId: "session_xyz"
}
```

### 4. Backend Executes Action

```typescript
// ActionsManager workflow:
// 1. Look up action instance
// 2. Load template by templateId
// 3. Create ActionContext with full capabilities
// 4. Call template's handler(params, context)
// 5. Log execution to .logs/actions/
// 6. Return result to client
// 7. Inject result message into chat session
```

### 5. Result Displayed

Result appears as new chat message and UI updates accordingly.

## Complete Template Examples

### 1. Send Payment Reminder to ACME Corp

```typescript
// agent/custom_scripts/actions/send-payment-reminder-to-acme.ts
import type { ActionTemplate, ActionContext, ActionResult } from "../types";

export const config: ActionTemplate = {
  id: "send_payment_reminder_acme",
  name: "Send Payment Reminder to ACME Corp",
  description: "Send a payment reminder email to ACME Corp for overdue invoices",
  icon: "💰",
  parameterSchema: {
    type: "object",
    properties: {
      invoiceNumber: {
        type: "string",
        description: "Invoice number (e.g., INV-2024-001)"
      },
      amount: {
        type: "string",
        description: "Invoice amount (e.g., '$5,000')"
      },
      dueDate: {
        type: "string",
        description: "Original due date"
      },
      daysPastDue: {
        type: "number",
        description: "Number of days past due"
      }
    },
    required: ["invoiceNumber", "amount", "dueDate", "daysPastDue"]
  }
};

export async function handler(
  params: Record<string, any>,
  context: ActionContext
): Promise<ActionResult> {
  const { invoiceNumber, amount, dueDate, daysPastDue } = params;
  const acmeEmail = "accounts.payable@acmecorp.com";

  context.log(`Sending payment reminder for ${invoiceNumber} to ACME Corp`);

  const body = `Hi ACME Accounts Payable Team,

This is a friendly reminder that Invoice ${invoiceNumber} for ${amount} was due on ${dueDate} and is now ${daysPastDue} days past due.

Please process payment at your earliest convenience. If you have already sent payment, please disregard this notice.

Invoice Details:
- Invoice #: ${invoiceNumber}
- Amount: ${amount}
- Due Date: ${dueDate}
- Days Past Due: ${daysPastDue}

If you have any questions or need a copy of the invoice, please let me know.

Best regards`;

  try {
    const result = await context.sendEmail({
      to: acmeEmail,
      subject: `Payment Reminder: Invoice ${invoiceNumber} - ${daysPastDue} Days Past Due`,
      body
    });

    context.notify(`Payment reminder sent to ACME Corp for ${invoiceNumber}`, {
      type: "success",
      priority: "normal"
    });

    return {
      success: true,
      message: `Payment reminder sent to ACME Corp for ${invoiceNumber}`,
      data: { messageId: result.messageId, invoiceNumber }
    };
  } catch (error) {
    context.log(`Failed to send payment reminder: ${error}`, "error");

    return {
      success: false,
      message: `Failed to send payment reminder: ${error.message}`
    };
  }
}
```

### 2. Archive Old Newsletters

```typescript
// agent/custom_scripts/actions/archive-old-newsletters.ts
import type { ActionTemplate, ActionContext, ActionResult } from "../types";

export const config: ActionTemplate = {
  id: "archive_old_newsletters",
  name: "Archive Old Newsletters",
  description: "Archive newsletter emails older than 30 days from TechCrunch, Morning Brew, and Hacker News",
  icon: "📰",
  parameterSchema: {
    type: "object",
    properties: {
      daysOld: {
        type: "number",
        description: "Archive newsletters older than this many days",
        default: 30
      }
    },
    required: []
  }
};

export async function handler(
  params: Record<string, any>,
  context: ActionContext
): Promise<ActionResult> {
  const { daysOld = 30 } = params;

  // Calculate date threshold
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - daysOld);
  const dateStr = cutoffDate.toISOString().split('T')[0].replace(/-/g, '/');

  // Gmail query for newsletters from specific senders older than threshold
  const query = `(from:newsletter@techcrunch.com OR from:crew@morningbrew.com OR from:noreply@hackernewsletter.com) before:${dateStr}`;

  context.log(`Archiving newsletters older than ${daysOld} days`);

  try {
    const emails = await context.emailAPI.searchWithGmailQuery(query);

    context.log(`Found ${emails.length} old newsletters to archive`);

    let archived = 0;
    for (const email of emails) {
      await context.archiveEmail(email.message_id);
      archived++;
    }

    context.notify(`Archived ${archived} old newsletters`, {
      type: "success",
      priority: "normal"
    });

    return {
      success: true,
      message: `Archived ${archived} newsletters older than ${daysOld} days`,
      data: {
        archivedCount: archived,
        daysOld,
        sources: ["TechCrunch", "Morning Brew", "Hacker News"]
      },
      refreshInbox: true
    };
  } catch (error) {
    context.log(`Failed to archive newsletters: ${error}`, "error");
    return {
      success: false,
      message: `Failed to archive newsletters: ${error.message}`
    };
  }
}
```

### 3. Summarize CEO Weekly Updates

```typescript
// agent/custom_scripts/actions/summarize-weekly-updates-from-ceo.ts
import type { ActionTemplate, ActionContext, ActionResult } from "../types";

export const config: ActionTemplate = {
  id: "summarize_ceo_weekly_updates",
  name: "Summarize CEO Weekly Updates",
  description: "Generate a concise summary of the CEO's weekly update emails focusing on key initiatives and decisions",
  icon: "📊",
  parameterSchema: {
    type: "object",
    properties: {
      weeksBack: {
        type: "number",
        description: "Number of weeks to look back (default: 4)",
        default: 4
      }
    },
    required: []
  }
};

export async function handler(
  params: Record<string, any>,
  context: ActionContext
): Promise<ActionResult> {
  const { weeksBack = 4 } = params;

  context.log(`Finding CEO weekly updates from the last ${weeksBack} weeks`);

  try {
    // Search for CEO updates (assuming CEO email is sarah.chen@company.com)
    const query = `from:sarah.chen@company.com subject:"Weekly Update" newer_than:${weeksBack * 7}d`;
    const emails = await context.emailAPI.searchWithGmailQuery(query);

    if (emails.length === 0) {
      return {
        success: false,
        message: `No CEO weekly updates found in the last ${weeksBack} weeks`
      };
    }

    context.log(`Found ${emails.length} CEO weekly updates`);

    // Sort by date, most recent first
    emails.sort((a, b) => new Date(b.received_at).getTime() - new Date(a.received_at).getTime());

    const emailContent = emails.map((e, idx) => {
      const date = new Date(e.received_at).toLocaleDateString();
      return `## Week ${emails.length - idx} (${date})\nSubject: ${e.subject}\n\n${e.body_text}`;
    }).join("\n\n---\n\n");

    const prompt = `You are reviewing the CEO's weekly update emails over the past ${weeksBack} weeks.

Please provide a comprehensive summary with the following sections:
1. **Key Strategic Initiatives**: Major projects and strategic priorities mentioned
2. **Important Decisions**: Key decisions made by leadership
3. **Company Performance**: Highlights about metrics, growth, or achievements
4. **Team Updates**: Significant hiring, org changes, or team accomplishments
5. **Action Items for Leadership**: Any calls to action or requests for the leadership team

Here are the emails:

${emailContent}`;

    const summary = await context.callAgent<string>({
      prompt,
      maxTokens: 2000
    });

    // Inject summary into chat
    context.addAssistantMessage(`📊 **CEO Weekly Updates Summary (Last ${weeksBack} weeks)**\n\n${summary}`);

    return {
      success: true,
      message: `Summarized ${emails.length} CEO weekly updates from the last ${weeksBack} weeks`,
      data: {
        summary,
        emailCount: emails.length,
        weeksBack
      }
    };
  } catch (error) {
    context.log(`Failed to summarize CEO updates: ${error}`, "error");
    return {
      success: false,
      message: `Failed to generate summary: ${error.message}`
    };
  }
}
```

### 4. Label Urgent Customer Support Emails

```typescript
// agent/custom_scripts/actions/label-urgent-customer-support.ts
import type { ActionTemplate, ActionContext, ActionResult } from "../types";

export const config: ActionTemplate = {
  id: "label_urgent_customer_support",
  name: "Label Urgent Customer Support Emails",
  description: "Identify and label customer support emails that contain urgent keywords or are from VIP customers",
  icon: "🚨",
  parameterSchema: {
    type: "object",
    properties: {
      hoursBack: {
        type: "number",
        description: "How many hours back to check (default: 24)",
        default: 24
      }
    },
    required: []
  }
};

export async function handler(
  params: Record<string, any>,
  context: ActionContext
): Promise<ActionResult> {
  const { hoursBack = 24 } = params;

  context.log(`Checking for urgent customer support emails from the last ${hoursBack} hours`);

  try {
    // Search for support emails from the last N hours
    const query = `to:support@company.com newer_than:${Math.ceil(hoursBack / 24)}d`;
    const emails = await context.emailAPI.searchWithGmailQuery(query);

    context.log(`Found ${emails.length} support emails`);

    // VIP customer domains
    const vipDomains = ["@bigclient.com", "@enterprise.com", "@fortune500.com"];

    // Urgent keywords
    const urgentKeywords = [
      "urgent", "asap", "critical", "emergency", "down",
      "not working", "broken", "immediately", "production issue",
      "security breach", "data loss"
    ];

    const urgentEmails = emails.filter(email => {
      // Check if from VIP domain
      const isVIP = vipDomains.some(domain => email.from_address.includes(domain));

      // Check for urgent keywords in subject or body
      const text = `${email.subject} ${email.body_text}`.toLowerCase();
      const hasUrgentKeyword = urgentKeywords.some(keyword => text.includes(keyword));

      return isVIP || hasUrgentKeyword;
    });

    context.log(`Identified ${urgentEmails.length} urgent support emails`);

    // Label urgent emails
    for (const email of urgentEmails) {
      await context.addLabel(email.message_id, "URGENT");
      await context.starEmail(email.message_id);
    }

    // Create a summary message
    const vipCount = urgentEmails.filter(e =>
      vipDomains.some(domain => e.from_address.includes(domain))
    ).length;

    const summaryMsg = urgentEmails.length > 0
      ? `🚨 Labeled ${urgentEmails.length} urgent support emails (${vipCount} from VIP customers)`
      : "No urgent support emails found";

    context.notify(summaryMsg, {
      type: urgentEmails.length > 0 ? "warning" : "info",
      priority: urgentEmails.length > 0 ? "high" : "normal"
    });

    return {
      success: true,
      message: summaryMsg,
      data: {
        totalChecked: emails.length,
        urgentFound: urgentEmails.length,
        vipCount
      },
      refreshInbox: true
    };
  } catch (error) {
    context.log(`Failed to label urgent support emails: ${error}`, "error");
    return {
      success: false,
      message: `Failed to label urgent emails: ${error.message}`
    };
  }
}
```

### 5. Forward Bug Reports to Engineering

```typescript
// agent/custom_scripts/actions/forward-bugs-to-engineering.ts
import type { ActionTemplate, ActionContext, ActionResult } from "../types";

export const config: ActionTemplate = {
  id: "forward_bugs_to_engineering",
  name: "Forward Bug Reports to Engineering",
  description: "Forward customer bug reports to the engineering team with priority assessment and reproduction steps",
  icon: "🐛",
  parameterSchema: {
    type: "object",
    properties: {
      emailId: {
        type: "string",
        description: "Bug report email to forward"
      },
      priority: {
        type: "string",
        description: "Bug priority level",
        enum: ["P0 - Critical", "P1 - High", "P2 - Medium", "P3 - Low"]
      },
      affectedFeature: {
        type: "string",
        description: "Which feature/component is affected"
      },
      reproducible: {
        type: "boolean",
        description: "Whether the bug is reproducible"
      }
    },
    required: ["emailId", "priority", "affectedFeature"]
  }
};

export async function handler(
  params: Record<string, any>,
  context: ActionContext
): Promise<ActionResult> {
  const { emailId, priority, affectedFeature, reproducible = false } = params;

  const engineeringTeam = "engineering@company.com";
  const engineeringSlack = "#eng-bugs";

  context.log(`Forwarding bug report to engineering team`);

  try {
    const email = await context.emailAPI.getEmailById(emailId);

    if (!email) {
      return {
        success: false,
        message: "Bug report email not found"
      };
    }

    // Extract key info using AI
    const analysisPrompt = `Analyze this bug report and extract:
1. Steps to reproduce (if mentioned)
2. Expected behavior
3. Actual behavior
4. User environment (browser, OS, version, etc.)
5. Error messages or screenshots mentioned

Bug Report:
From: ${email.from_address}
Subject: ${email.subject}

${email.body_text}

Provide the analysis in a structured format.`;

    const analysis = await context.callAgent<string>({
      prompt: analysisPrompt,
      maxTokens: 1000
    });

    const forwardBody = `🐛 **Bug Report Forwarded from Customer Support**

**Priority:** ${priority}
**Affected Feature:** ${affectedFeature}
**Reproducible:** ${reproducible ? "Yes" : "Unknown"}
**Reporter:** ${email.from_address}
**Reported:** ${new Date(email.received_at).toLocaleString()}

---

**AI Analysis:**

${analysis}

---

**Original Email:**

Subject: ${email.subject}

${email.body_text}

---

Please investigate and update the ticket in Linear. Tag in ${engineeringSlack} when you have an update.`;

    await context.sendEmail({
      to: engineeringTeam,
      subject: `[${priority}] Bug Report: ${affectedFeature} - ${email.subject}`,
      body: forwardBody
    });

    // Add label to original email
    await context.addLabel(emailId, "FORWARDED_TO_ENG");

    context.notify(`Bug report forwarded to engineering team`, {
      type: "success",
      priority: priority.startsWith("P0") || priority.startsWith("P1") ? "high" : "normal"
    });

    return {
      success: true,
      message: `Bug report forwarded to engineering team with ${priority} priority`,
      data: {
        priority,
        affectedFeature,
        originalSender: email.from_address
      }
    };
  } catch (error) {
    context.log(`Failed to forward bug report: ${error}`, "error");
    return {
      success: false,
      message: `Failed to forward bug report: ${error.message}`
    };
  }
}
```

## ActionsManager Implementation

```typescript
// ccsdk/actions-manager.ts
import { readdir, watch, appendFile, mkdir } from "fs/promises";
import { join } from "path";
import { existsSync } from "fs";
import type {
  ActionTemplate,
  ActionInstance,
  ActionContext,
  ActionResult,
  ActionLogEntry
} from "../agent/custom_scripts/types";

export class ActionsManager {
  private actionsDir = join(process.cwd(), "agent/custom_scripts/actions");
  private logsDir = join(process.cwd(), "agent/custom_scripts/.logs/actions");
  private templates: Map<string, {
    config: ActionTemplate;
    handler: Function;
  }> = new Map();
  private instances: Map<string, ActionInstance> = new Map();

  constructor() {
    this.ensureLogsDir();
  }

  /**
   * Ensure logs directory exists
   */
  private async ensureLogsDir() {
    if (!existsSync(this.logsDir)) {
      await mkdir(this.logsDir, { recursive: true });
    }
  }

  /**
   * Load all action templates from directory
   */
  async loadAllTemplates(): Promise<ActionTemplate[]> {
    this.templates.clear();

    try {
      const files = await readdir(this.actionsDir);

      for (const file of files) {
        if (file.endsWith(".ts") && !file.startsWith("_")) {
          await this.loadTemplate(file);
        }
      }
    } catch (error) {
      console.error("Error loading action templates:", error);
    }

    return Array.from(this.templates.values()).map(t => t.config);
  }

  /**
   * Load a single template file
   */
  private async loadTemplate(filename: string) {
    try {
      const filePath = join(this.actionsDir, filename);
      const module = await import(`${filePath}?t=${Date.now()}`);

      if (module.config?.id && typeof module.handler === "function") {
        this.templates.set(module.config.id, {
          config: module.config,
          handler: module.handler
        });
        console.log(`Loaded action template: ${module.config.id}`);
      } else {
        console.warn(`Invalid action template ${filename}: missing config or handler`);
      }
    } catch (error) {
      console.error(`Error loading template ${filename}:`, error);
    }
  }

  /**
   * Get template by ID
   */
  getTemplate(id: string): ActionTemplate | undefined {
    return this.templates.get(id)?.config;
  }

  /**
   * Get all templates
   */
  getAllTemplates(): ActionTemplate[] {
    return Array.from(this.templates.values()).map(t => t.config);
  }

  /**
   * Register an action instance created by agent
   */
  registerInstance(instance: ActionInstance): void {
    this.instances.set(instance.instanceId, instance);
  }

  /**
   * Get action instance by ID
   */
  getInstance(instanceId: string): ActionInstance | undefined {
    return this.instances.get(instanceId);
  }

  /**
   * Execute an action instance
   */
  async executeAction(
    instanceId: string,
    context: ActionContext
  ): Promise<ActionResult> {
    const startTime = Date.now();
    const instance = this.instances.get(instanceId);

    if (!instance) {
      return {
        success: false,
        message: "Action instance not found"
      };
    }

    const template = this.templates.get(instance.templateId);

    if (!template) {
      return {
        success: false,
        message: `Template "${instance.templateId}" not found`
      };
    }

    let result: ActionResult;
    let error: string | undefined;

    try {
      // Execute handler
      context.log(`Executing action: ${instance.label}`);
      result = await template.handler(instance.params, context);
    } catch (err) {
      error = err.message;
      result = {
        success: false,
        message: `Action failed: ${error}`
      };
      context.log(`Action failed: ${error}`, "error");
    }

    const duration = Date.now() - startTime;

    // Log execution
    await this.logExecution({
      timestamp: new Date().toISOString(),
      instanceId: instance.instanceId,
      templateId: instance.templateId,
      sessionId: instance.sessionId,
      params: instance.params,
      result,
      duration,
      error
    });

    return result;
  }

  /**
   * Log action execution to JSONL file
   */
  private async logExecution(entry: ActionLogEntry) {
    try {
      const date = new Date().toISOString().split("T")[0];
      const logFile = join(this.logsDir, `${date}.jsonl`);
      await appendFile(logFile, JSON.stringify(entry) + "\n");
    } catch (error) {
      console.error("Failed to log action execution:", error);
    }
  }

  /**
   * Watch for template file changes
   */
  async watchTemplates(onChange: (templates: ActionTemplate[]) => void) {
    try {
      const watcher = watch(this.actionsDir);

      for await (const event of watcher) {
        if (event.filename?.endsWith(".ts")) {
          console.log(`Action template ${event.eventType}: ${event.filename}`);
          const templates = await this.loadAllTemplates();
          onChange(templates);
        }
      }
    } catch (error) {
      console.error("Error watching action templates:", error);
    }
  }

  /**
   * Get execution logs for a template
   */
  async getTemplateLogs(templateId: string, limit: number = 50): Promise<ActionLogEntry[]> {
    // Implementation would read JSONL files and filter by templateId
    // Similar to ListenersManager.getListenerLogs()
    return [];
  }

  /**
   * Clean up old action instances (optional)
   */
  pruneInstances(maxAge: number = 3600000) { // 1 hour default
    const now = Date.now();
    for (const [id, instance] of this.instances) {
      const age = now - new Date(instance.createdAt).getTime();
      if (age > maxAge) {
        this.instances.delete(id);
      }
    }
  }
}
```

## WebSocket Protocol

### Server → Client Messages

```typescript
// Send available templates (on connection)
{
  type: "action_templates",
  templates: ActionTemplate[]
}

// Send action instances (generated by agent during conversation)
{
  type: "action_instances",
  actions: ActionInstance[],
  sessionId: string
}

// Action execution result
{
  type: "action_result",
  instanceId: string,
  result: ActionResult,
  sessionId: string
}
```

### Client → Server Messages

```typescript
// Execute action
{
  type: "execute_action",
  instanceId: string,
  sessionId: string
}
```

## Frontend Integration

### ActionButton Component

```typescript
// client/components/ActionButton.tsx
import { ActionInstance } from "../types";

interface ActionButtonProps {
  action: ActionInstance;
  onExecute: (instanceId: string) => void;
  loading?: boolean;
}

export function ActionButton({ action, onExecute, loading }: ActionButtonProps) {
  const styleClass = {
    primary: "bg-blue-600 hover:bg-blue-700 text-white",
    secondary: "bg-gray-200 hover:bg-gray-300 text-gray-800",
    danger: "bg-red-600 hover:bg-red-700 text-white"
  }[action.style || "primary"];

  return (
    <button
      onClick={() => onExecute(action.instanceId)}
      disabled={loading}
      className={`
        px-3 py-1.5 rounded-md text-sm font-medium
        transition-colors duration-200
        disabled:opacity-50 disabled:cursor-not-allowed
        ${styleClass}
      `}
      title={action.description}
    >
      {loading ? "..." : action.label}
    </button>
  );
}
```

### Actions in Chat Messages

```typescript
// client/components/MessageRenderer.tsx
import { ActionButton } from "./ActionButton";

export function MessageRenderer({ message }: { message: Message }) {
  const { sendMessage } = useWebSocket();

  const handleActionExecute = (instanceId: string) => {
    sendMessage({
      type: "execute_action",
      instanceId,
      sessionId: message.sessionId
    });
  };

  return (
    <div className="message">
      {/* Message content */}
      <div>{message.content}</div>

      {/* Action buttons if present */}
      {message.actions && message.actions.length > 0 && (
        <div className="flex gap-2 mt-3 flex-wrap">
          {message.actions.map(action => (
            <ActionButton
              key={action.instanceId}
              action={action}
              onExecute={handleActionExecute}
            />
          ))}
        </div>
      )}
    </div>
  );
}
```

## Agent Integration

### How Agent Creates Actions

The agent can create action instances during conversation by including them in responses:

```typescript
// Agent's response includes actions metadata
// This is handled by the backend Session class

{
  content: "I found that ACME Corp's invoice is 15 days overdue. Here's what you can do:",
  actions: [
    {
      instanceId: "act_" + generateId(),
      templateId: "send_payment_reminder_acme",
      label: "Send payment reminder to ACME Corp for Invoice #2024-001",
      description: "Invoice #2024-001 ($5,000) is 15 days past due",
      params: {
        invoiceNumber: "INV-2024-001",
        amount: "$5,000",
        dueDate: "December 15, 2024",
        daysPastDue: 15
      },
      style: "primary",
      sessionId: currentSessionId,
      createdAt: new Date().toISOString()
    }
  ]
}
```

### System Prompt Addition

Add to agent's system prompt:

```markdown
## Action Capabilities

You can create interactive action buttons that users can click to execute specific operations.

### Available Action Templates:

{INSERT LIST OF TEMPLATES HERE - dynamically loaded}

### How to Create Actions:

When responding to the user, you can include action instances in your response. The system will automatically render them as clickable buttons.

Include actions in a special format:

```json
{
  "actions": [
    {
      "templateId": "send_payment_reminder_acme",
      "label": "Send payment reminder to ACME Corp for Invoice #2024-001",
      "params": {
        "invoiceNumber": "INV-2024-001",
        "amount": "$5,000",
        "dueDate": "December 15, 2024",
        "daysPastDue": 15
      },
      "style": "primary"
    }
  ]
}
```

### Guidelines:

1. **Be Highly Specific**: Actions should be extremely specific to the user's actual workflow, not generic
   - Good: "Send payment reminder to ACME Corp for Invoice #2024-001 (15 days overdue, $5,000)"
   - Bad: "Send email"
   - Templates themselves should be user-specific (e.g., "send_payment_reminder_acme" not "send_email")

2. **Provide Rich Context**: Include description with full details about what this action does

3. **Choose Appropriate Style**:
   - `primary`: Main recommended actions
   - `secondary`: Optional/alternative actions
   - `danger`: Destructive/risky actions (delete, bulk archive, etc.)

4. **Use Correct Template**: Match the template to the specific operation needed

5. **Validate Parameters**: Ensure all required parameters are provided per template schema

6. **Templates are User-Specific**: Each action template should be tailored to the user's specific workflows, vendors, teams, and processes
```

## Logging & Audit Trail

All action executions logged to `.logs/actions/{date}.jsonl`:

```json
{
  "timestamp": "2025-01-06T10:30:45.123Z",
  "instanceId": "act_abc123",
  "templateId": "send_email",
  "sessionId": "session_xyz",
  "params": {
    "to": "vendor@acme.com",
    "subject": "Payment Reminder",
    "body": "..."
  },
  "result": {
    "success": true,
    "message": "Email sent successfully",
    "data": { "messageId": "msg_456" }
  },
  "duration": 1250,
  "error": null
}
```

## Error Handling

### Invalid Template
```typescript
if (!template) {
  return {
    success: false,
    message: `Template "${templateId}" not found. Available templates: ${availableIds.join(", ")}`
  };
}
```

### Missing Parameters
```typescript
// Validate against parameterSchema before execution
const validation = validateParams(params, template.config.parameterSchema);
if (!validation.valid) {
  return {
    success: false,
    message: `Invalid parameters: ${validation.errors.join(", ")}`
  };
}
```

### Handler Errors
```typescript
try {
  result = await handler(params, context);
} catch (error) {
  return {
    success: false,
    message: `Action failed: ${error.message}`
  };
}
```

## Best Practices

1. **User-Specific Templates**: Create templates tailored to the user's specific workflows, vendors, customers, and processes (e.g., `send-payment-reminder-to-acme`, not `send-email`)

2. **Descriptive Naming**: Use specific verb-noun-context format (e.g., `forward-bugs-to-engineering`, `archive-old-newsletters`, `summarize-ceo-weekly-updates`)

3. **Very Specific Instances**: Agent should create highly specific action instances with full context in the label and description

4. **Parameter Validation**: Validate all parameters against schema before execution

5. **Error Messages**: Provide clear, actionable error messages

6. **Logging**: Log all executions for audit trail and debugging

7. **Idempotency**: Design handlers to be idempotent when possible

8. **Resource Cleanup**: Prune old instances periodically

9. **Testing**: Test handlers thoroughly with edge cases and real user data

10. **Documentation**: Document parameter schemas clearly with examples

